diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..35fdcca --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# git +.git/ +.gitignore + +# vim +**/*.swp + +# database +pgdata/ + +# envvars +.env +.env.example + +# docker +.dockerignore +docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fc1239b --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# docker-compose configuration for postgres +DB_PORT=5432 +DB_VOLUME="/absolute/path/to/pgdata" + +# postgres startup variables +POSTGRES_USER=discordoragi +POSTGRES_PASSWORD="CHANGE THIS TO A SAFE PASSWORD" +POSTGRES_DB=discordoragi + +# connection info for db +DB_URL="postgresql://" +DB_HOST=db diff --git a/.gitignore b/.gitignore index 35f0e3b..365ed06 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,12 @@ oauth.ini #Pycharm .idea .vscode + +# Vim +**/*.swp + +# Configuration +.env + +# Database +pgdata/ diff --git a/.python-version b/.python-version deleted file mode 100644 index b727628..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.6.2 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ab53d07 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM ubuntu:latest + +ENV PATH="/root/.local/bin:$PATH" + +WORKDIR /app + +COPY pyproject.toml . + +RUN apt update && apt install -y tree vim postgresql-client build-essential libssl-dev libffi-dev python3 python3-dev python3-pip python3-venv && pip install pipx && pipx install poetry && poetry install + +COPY . . diff --git a/cogs/__init__.py b/cogs/__init__.py deleted file mode 100644 index 550a448..0000000 --- a/cogs/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from cogs.search import Search - -__all__ = ['Search'] diff --git a/discordoragi/__init__.py b/discordoragi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/__init__.py b/discordoragi/bot/__init__.py similarity index 57% rename from bot/__init__.py rename to discordoragi/bot/__init__.py index 6907a7b..482bcea 100644 --- a/bot/__init__.py +++ b/discordoragi/bot/__init__.py @@ -1,5 +1,4 @@ - -from bot.discordoragi import Discordoragi +from .discordoragi import Discordoragi __all__ = ['Discordoragi', 'SessionManager', 'HTTPStatusError'] diff --git a/bot/discordoragi.py b/discordoragi/bot/discordoragi.py similarity index 91% rename from bot/discordoragi.py rename to discordoragi/bot/discordoragi.py index de2b941..6de2fec 100644 --- a/bot/discordoragi.py +++ b/discordoragi/bot/discordoragi.py @@ -5,8 +5,8 @@ import yaml from time import time from aiohttp_wrapper import SessionManager -from helpers.discord_helpers import get_name_with_discriminator -from helpers import PostgresController +from ..helpers.discord_helpers import get_name_with_discriminator +from ..helpers import PostgresController from logging import Formatter, INFO, StreamHandler, getLogger diff --git a/discordoragi/cogs/__init__.py b/discordoragi/cogs/__init__.py new file mode 100644 index 0000000..4821b33 --- /dev/null +++ b/discordoragi/cogs/__init__.py @@ -0,0 +1,3 @@ +from .search import Search + +__all__ = ['Search'] diff --git a/cogs/search.py b/discordoragi/cogs/search.py similarity index 100% rename from cogs/search.py rename to discordoragi/cogs/search.py diff --git a/config/example_config.yml b/discordoragi/config/example_config.yml similarity index 100% rename from config/example_config.yml rename to discordoragi/config/example_config.yml diff --git a/helpers/__init__.py b/discordoragi/helpers/__init__.py similarity index 100% rename from helpers/__init__.py rename to discordoragi/helpers/__init__.py diff --git a/helpers/database_helpers.py b/discordoragi/helpers/database_helpers.py similarity index 100% rename from helpers/database_helpers.py rename to discordoragi/helpers/database_helpers.py diff --git a/helpers/discord_helpers.py b/discordoragi/helpers/discord_helpers.py similarity index 100% rename from helpers/discord_helpers.py rename to discordoragi/helpers/discord_helpers.py diff --git a/run.py b/discordoragi/run.py similarity index 81% rename from run.py rename to discordoragi/run.py index acbc13b..8f69518 100644 --- a/run.py +++ b/discordoragi/run.py @@ -1,8 +1,8 @@ """ Actually runs the code """ -from bot import Discordoragi -from cogs import Search +from .bot import Discordoragi +from .cogs import Search from asyncio import get_event_loop diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4aaccbe --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3.8' +services: + db: + image: postgres:14-alpine + restart: always + volumes: + - discordoragi_pgdata:/var/lib/postgresql/data + expose: + - "${DB_PORT}" + environment: + POSTGRES_USER: "${POSTGRES_USER}" + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" + POSTGRES_DB: "${POSTGRES_DB}" + test: + build: . + image: discordoragi_test:latest + depends_on: + - db + environment: + DB_URL: "${DB_URL}" + DB_USER: "${POSTGRES_USER}" + DB_PASSWORD: "${POSTGRES_PASSWORD}" + DB_HOST: "${DB_HOST}" + DB_PORT: "${DB_PORT}" + DB_DATABASE: "${POSTGRES_DB}" + command: ["sleep", "infinity"] +volumes: + discordoragi_pgdata: + driver: local + driver_opts: + type: none + o: bind + device: "${DB_VOLUME}" diff --git a/pgdata/.gitkeep b/pgdata/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..f5796c8 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,544 @@ +[[package]] +name = "aiohttp" +version = "3.7.4.post0" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=3.0,<4.0" +attrs = ">=17.3.0" +chardet = ">=2.0,<5.0" +multidict = ">=4.5,<7.0" +typing-extensions = ">=3.6.5" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotlipy", "cchardet"] + +[[package]] +name = "aiohttp-wrapper" +version = "1.0.0" +description = "Abstraction of HTTP requests using aiohttp" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +aiohttp = ">=2.2.5" +xmltodict = ">=0.11.0" + +[[package]] +name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[[package]] +name = "asyncpg" +version = "0.26.0" +description = "An asyncio PostgreSQL driver" +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "flake8 (>=3.9.2,<3.10.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "uvloop (>=0.15.3)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["flake8 (>=3.9.2,<3.10.0)", "pycodestyle (>=2.7.0,<2.8.0)", "uvloop (>=0.15.3)"] + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "chardet" +version = "4.0.0" +description = "Universal encoding detector for Python 2 and 3" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "cssselect" +version = "1.1.0" +description = "cssselect parses CSS3 Selectors and translates them to XPath 1.0" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "discord.py" +version = "1.7.3" +description = "A Python wrapper for the Discord API" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[package.dependencies] +aiohttp = ">=3.6.0,<3.8.0" + +[package.extras] +docs = ["sphinx (==3.0.3)", "sphinxcontrib-trio (==1.1.2)", "sphinxcontrib-websupport"] +voice = ["PyNaCl (>=1.3.0,<1.5)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "lxml" +version = "4.9.1" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "minoshiro" +version = "0.1.9" +description = "An async Python3.6 library to search for anime, manga andlight novel using various web apis." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +aiohttp = ">=2.2.5" +aiohttp-wrapper = ">=1.0.0" +pyquery = ">=1.2.17" +xmltodict = ">=0.11.0" + +[package.extras] +postgres = ["asyncpg (>=0.12.0)"] + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pyquery" +version = "1.4.3" +description = "A jquery-like library for python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +cssselect = ">0.7.9" +lxml = ">=2.1" + +[[package]] +name = "PyYAML" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "xmltodict" +version = "0.13.0" +description = "Makes working with XML feel like you are working with JSON" +category = "main" +optional = false +python-versions = ">=3.4" + +[[package]] +name = "yarl" +version = "1.8.1" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.10" +content-hash = "ecb23ade9527f612b99a044e82ddf6269fe4bb6349f60a7c36a0b27865579f93" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win32.whl", hash = "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287"}, + {file = "aiohttp-3.7.4.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win32.whl", hash = "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f"}, + {file = "aiohttp-3.7.4.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win32.whl", hash = "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16"}, + {file = "aiohttp-3.7.4.post0-cp38-cp38-win_amd64.whl", hash = "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win32.whl", hash = "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9"}, + {file = "aiohttp-3.7.4.post0-cp39-cp39-win_amd64.whl", hash = "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe"}, + {file = "aiohttp-3.7.4.post0.tar.gz", hash = "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf"}, +] +aiohttp-wrapper = [ + {file = "aiohttp_wrapper-1.0.0-py3-none-any.whl", hash = "sha256:bef4d545e34ed9cbca3ebf52c0cd6d757f6e90bad957d471bef88baf393b26c2"}, + {file = "aiohttp_wrapper-1.0.0.tar.gz", hash = "sha256:f96acd3a06a3ab79759e1718bcec309e3a76976e5d8594bb270e81c4e741d3ad"}, +] +async-timeout = [ + {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, + {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, +] +asyncpg = [ + {file = "asyncpg-0.26.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ed3880b3aec8bda90548218fe0914d251d641f798382eda39a17abfc4910af0"}, + {file = "asyncpg-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5bd99ee7a00e87df97b804f178f31086e88c8106aca9703b1d7be5078999e68"}, + {file = "asyncpg-0.26.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:868a71704262834065ca7113d80b1f679609e2df77d837747e3d92150dd5a39b"}, + {file = "asyncpg-0.26.0-cp310-cp310-win32.whl", hash = "sha256:838e4acd72da370ad07243898e886e93d3c0c9413f4444d600ba60a5cc206014"}, + {file = "asyncpg-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:a254d09a3a989cc1839ba2c34448b879cdd017b528a0cda142c92fbb6c13d957"}, + {file = "asyncpg-0.26.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3ecbe8ed3af4c739addbfbd78f7752866cce2c4e9cc3f953556e4960349ae360"}, + {file = "asyncpg-0.26.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ce7d8c0ab4639bbf872439eba86ef62dd030b245ad0e17c8c675d93d7a6b2d"}, + {file = "asyncpg-0.26.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7129bd809990fd119e8b2b9982e80be7712bb6041cd082be3e415e60e5e2e98f"}, + {file = "asyncpg-0.26.0-cp36-cp36m-win32.whl", hash = "sha256:03f44926fa7ff7ccd59e98f05c7e227e9de15332a7da5bbcef3654bf468ee597"}, + {file = "asyncpg-0.26.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b1f7b173af649b85126429e11a628d01a5b75973d2a55d64dba19ad8f0e9f904"}, + {file = "asyncpg-0.26.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efe056fd22fc6ed5c1ab353b6510808409566daac4e6f105e2043797f17b8dad"}, + {file = "asyncpg-0.26.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d96cf93e01df9fb03cef5f62346587805e6c0ca6f654c23b8d35315bdc69af59"}, + {file = "asyncpg-0.26.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:235205b60d4d014921f7b1cdca0e19669a9a8978f7606b3eb8237ca95f8e716e"}, + {file = "asyncpg-0.26.0-cp37-cp37m-win32.whl", hash = "sha256:0de408626cfc811ef04f372debfcdd5e4ab5aeb358f2ff14d1bdc246ed6272b5"}, + {file = "asyncpg-0.26.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f92d501bf213b16fabad4fbb0061398d2bceae30ddc228e7314c28dcc6641b79"}, + {file = "asyncpg-0.26.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9acb22a7b6bcca0d80982dce3d67f267d43e960544fb5dd934fd3abe20c48014"}, + {file = "asyncpg-0.26.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e550d8185f2c4725c1e8d3c555fe668b41bd092143012ddcc5343889e1c2a13d"}, + {file = "asyncpg-0.26.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:050e339694f8c5d9aebcf326ca26f6622ef23963a6a3a4f97aeefc743954afd5"}, + {file = "asyncpg-0.26.0-cp38-cp38-win32.whl", hash = "sha256:b0c3f39ebfac06848ba3f1e280cb1fada7cc1229538e3dad3146e8d1f9deb92a"}, + {file = "asyncpg-0.26.0-cp38-cp38-win_amd64.whl", hash = "sha256:49fc7220334cc31d14866a0b77a575d6a5945c0fa3bb67f17304e8b838e2a02b"}, + {file = "asyncpg-0.26.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d156e53b329e187e2dbfca8c28c999210045c45ef22a200b50de9b9e520c2694"}, + {file = "asyncpg-0.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b4051012ca75defa9a1dc6b78185ca58cdc3a247187eb76a6bcf55dfaa2fad4"}, + {file = "asyncpg-0.26.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d60f15a0ac18c54a6ca6507c28599c06e2e87a0901e7b548f15243d71905b18"}, + {file = "asyncpg-0.26.0-cp39-cp39-win32.whl", hash = "sha256:ede1a3a2c377fe12a3930f4b4dd5340e8b32929541d5db027a21816852723438"}, + {file = "asyncpg-0.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:8e1e79f0253cbd51fc43c4d0ce8804e46ee71f6c173fdc75606662ad18756b52"}, + {file = "asyncpg-0.26.0.tar.gz", hash = "sha256:77e684a24fee17ba3e487ca982d0259ed17bae1af68006f4cf284b23ba20ea2c"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +chardet = [ + {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, + {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, +] +cssselect = [ + {file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"}, + {file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"}, +] +"discord.py" = [ + {file = "discord.py-1.7.3-py3-none-any.whl", hash = "sha256:c6f64db136de0e18e090f6752ea68bdd4ab0a61b82dfe7acecefa22d6477bb0c"}, + {file = "discord.py-1.7.3.tar.gz", hash = "sha256:462cd0fe307aef8b29cbfa8dd613e548ae4b2cb581d46da9ac0d46fb6ea19408"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +lxml = [ + {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"}, + {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"}, + {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"}, + {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"}, + {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"}, + {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"}, + {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"}, + {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"}, + {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"}, + {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"}, + {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"}, + {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"}, + {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"}, + {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"}, + {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"}, + {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"}, + {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"}, + {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"}, + {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"}, + {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"}, + {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"}, + {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"}, + {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"}, + {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"}, + {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"}, + {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"}, + {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"}, + {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"}, + {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"}, + {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"}, + {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"}, + {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"}, + {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"}, + {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"}, + {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"}, + {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"}, + {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"}, + {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"}, + {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"}, + {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"}, + {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"}, +] +minoshiro = [ + {file = "minoshiro-0.1.9.tar.gz", hash = "sha256:41e5a9c58ac0314a30a93d97c5560648eb3c31a0b724cbb41cbaccb13c15f453"}, +] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +pyquery = [ + {file = "pyquery-1.4.3-py3-none-any.whl", hash = "sha256:1fc33b7699455ed25c75282bc8f80ace1ac078b0dda5a933dacbd8b1c1f83963"}, + {file = "pyquery-1.4.3.tar.gz", hash = "sha256:a388eefb6bc4a55350de0316fbd97cda999ae669b6743ae5b99102ba54f5aa72"}, +] +PyYAML = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +xmltodict = [ + {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, + {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, +] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..323b1df --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[tool.poetry] +name = "Discordoragi" +version = "4.0.0a" +description = "Discordoragi is a Discord bot usign the Minoshiro library which creates anime and manga links from MAL, Anilist, MangaUpdates, and Anime-Planet when requested." +license = "GPL-2.0-or-later" +authors = [ + "Alex Portlock 'Nihilate' <>", + "James Wolff 'jwolff52' ", + "dashwav ", + "Amndeep Singh Mann 'Amndeep7' " +] + +[tool.poetry.dependencies] +python = "^3.10" +minoshiro = "^0.1.9" +"discord.py" = "^1.7.3" +pyyaml = "^6.0" +asyncpg = "^0.26.0" + +[tool.poetry.scripts] +discordoragi = "discordoragi.run:run" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0625f20..0000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -requests==2.7.0 -six==1.9.0 -minoshiro==0.1.9 -psycopg2==2.7.0 -pyquery==1.2.13 -discord.py==1.3.4 diff --git a/roboragi_old/AniDB.py b/roboragi_old/AniDB.py deleted file mode 100644 index f32dfa0..0000000 --- a/roboragi_old/AniDB.py +++ /dev/null @@ -1,83 +0,0 @@ -''' -AniDB.py -Handles all AniDB information -''' - -from pyquery import PyQuery as pq -import aiohttp -import urllib -import difflib -import traceback -import pprint - -session = aiohttp.ClientSession() - -async def getAnimeURL(searchText): - cleanSearchText = urllib.parse.quote(searchText) - try: - async with session.get('http://anisearch.outrance.pl/?task=search&query=' + cleanSearchText, timeout=10) as resp: - html = await resp.read() - anidb = pq(html) - except: - traceback.print_exc() - return None - - animeList = [] - - for anime in anidb('animetitles anime'): - titles = [] - for title in pq(anime).find('title').items(): - titleInfo = {} - titleInfo['title'] = title.text() - titleInfo['lang'] = title.attr['lang'] - titles.append(titleInfo) - - url = 'http://anidb.net/a' + anime.attrib['aid'] - - if titles: - data = { 'titles': titles, - 'url': url - } - - animeList.append(data) - - closest = getClosestAnime(searchText, animeList) - - if closest: - return closest['url'] - else: - return None - -def getAnimeURLById(animeId): - return 'http://anidb.net/a' + str(animeId) - -def getClosestAnime(searchText, animeList): - nameList = [] - - trustedNames = [] #i.e. English/default names - untrustedNames = [] #everything else (French, Italian etc) - - for anime in animeList: - for title in anime['titles']: - if title['lang'].lower() in ['x-jat', 'en']: - trustedNames.append(title['title'].lower()) - else: - untrustedNames.append(title['title'].lower()) - - closestNameFromList = difflib.get_close_matches(searchText.lower(), trustedNames, 1, 0.85) - - if closestNameFromList: - for anime in animeList: - for title in anime['titles']: - if closestNameFromList[0].lower() == title['title'].lower() and title['lang'].lower() in ['x-jat', 'en']: - return anime - else: - closestNameFromList = difflib.get_close_matches(searchText.lower(), untrustedNames, 1, 0.85) - - if closestNameFromList: - for anime in animeList: - for title in anime['titles']: - if closestNameFromList[0].lower() == title['title'].lower() and title['lang'].lower() not in ['x-jat', 'en']: - return anime - - return None diff --git a/roboragi_old/Anilist.py b/roboragi_old/Anilist.py deleted file mode 100644 index 96e1599..0000000 --- a/roboragi_old/Anilist.py +++ /dev/null @@ -1,372 +0,0 @@ - -""" -Anilist.py -Handles all of the connections to Anilist. -""" -import DatabaseHandler -import aiohttp -import urllib -import difflib -import traceback -import pprint -import asyncio -ANICLIENT = '' -ANISECRET = '' - -session = aiohttp.ClientSession() - -try: - import Config - ANICLIENT = Config.aniclient - ANISECRET = Config.anisecret -except ImportError: - pass - -access_token = '' - -escape_table = { - "&": " ", - "\'": "\\'", - '\"': '\\"', - '/': ' ', - '-': ' ' - #'!': '\!' - } - -#Anilist's database doesn't like weird symbols when searching it, so you have to escape or replace a bunch of stuff. -def escape(text): - return "".join(escape_table.get(c,c) for c in text) - -def getSynonyms(request): - synonyms = [] - - synonyms.append(request['title_english']) if request['title_english'] else None - synonyms.append(request['title_romaji']) if request['title_romaji'] else None - synonyms.extend(request['synonyms']) if request['synonyms'] else None - - return synonyms - -#Sets up the connection to Anilist. You need a token to get stuff from them, which expires every hour. -async def setup(): - print('Setting up AniList') - loop = asyncio.get_event_loop() - try: - async with session.post('https://anilist.co/api/auth/access_token', params={'grant_type':'client_credentials', 'client_id':ANICLIENT, 'client_secret':ANISECRET}) as resp: - request = await resp.json() - global access_token - access_token = request['access_token'] - except Exception as e: - print('Error getting Anilist token: '+ e) - -#Returns the closest anime (as a Json-like object) it can find using the given searchtext -async def getAnimeDetails(searchText): - cachedAnime = DatabaseHandler.checkForMalEntry('anilistanime', searchText) - if cachedAnime is not None: - if cachedAnime['update']: - print("found cached anime, needs update in anilist") - return await getAnimeDetailsById(cachedAnime['id']) - else: - print("found cached anime, doesn't need update in anilist") - return cachedAnime['content'] - try: - #htmlSearchText = escape(searchText) - htmlSearchText = urllib.parse.quote(searchText) - async with session.get("https://anilist.co/api/anime/search/" + htmlSearchText, params={'access_token':access_token}, timeout=10) as resp: - if resp.status != 200: - await setup() - request = await session.get("https://anilist.co/api/anime/search/" + htmlSearchText, params={'access_token':access_token}, timeout=10) - - request = await resp.json() - - #Of the given list of shows, we try to find the one we think is closest to our search term - closestAnime = getClosestAnime(searchText, request) - - if closestAnime: - fullDetails = await getFullAnimeDetails(closestAnime['id']) - return fullDetails - else: - return None - - except Exception as e: - traceback.print_exc() - return None - -#Returns the anime details based on an id -async def getAnimeDetailsById(animeID): - try: - return await getFullAnimeDetails(animeID) - except Exception as e: - return None - -#Gets the "full" anime details (which aren't displayed when we search using the basic function). Gives us cool data like time until the next episode is aired. -async def getFullAnimeDetails(animeID): - try: - async with session.get("https://anilist.co/api/anime/" + str(animeID), params={'access_token':access_token}, timeout=10) as resp: - if resp.status != 200: - await setup() - resp = await session.get("https://anilist.co/api/anime/" + str(animeID), params={'access_token':access_token}, timeout=10) - - - if resp.status == 200: - request = await resp.json() - request['genres'] = [genre for genre in request['genres'] if genre] - request['synonyms'] = [synonym for synonym in request['synonyms'] if synonym] - - return request - else: - return None - except Exception as e: - print("Error finding anime:{} in anilist.\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - -#Given a list, it finds the closest anime series it can. -def getClosestAnime(searchText, animeList): - try: - animeNameList = [] - animeNameListNoSyn = [] - - #For each anime series, add all the titles/synonyms to an array and do a fuzzy string search to find the one closest to our search text. - #We also fill out an array that doesn't contain the synonyms. This is to protect against shows with multiple adaptations and similar synonyms (e.g. Haiyore Nyaruko-San) - for anime in animeList: - if 'title_english' in anime: - animeNameList.append(anime['title_english'].lower()) - animeNameListNoSyn.append(anime['title_english'].lower()) - - if 'title_romaji' in anime: - animeNameList.append(anime['title_romaji'].lower()) - animeNameListNoSyn.append(anime['title_romaji'].lower()) - - if 'synonyms' in anime: - for synonym in anime['synonyms']: - animeNameList.append(synonym.lower()) - - closestNameFromList = difflib.get_close_matches(searchText.lower(), animeNameList, 1, 0.95)[0] - - for anime in animeList: - if (anime['title_english'].lower() == closestNameFromList.lower()) or (anime['title_romaji'].lower() == closestNameFromList.lower()): - return anime - else: - for synonym in anime['synonyms']: - if (synonym.lower() == closestNameFromList.lower()) and (synonym.lower() not in animeNameListNoSyn): - return anime - - return None - except Exception as e: - print("Error finding anime:{} in anilist.\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - -#Makes a search for a manga series using a specific author -async def getMangaWithAuthor(searchText, authorName): - try: - - async with session.get("https://anilist.co/api/manga/search/" + searchText, params={'access_token':access_token}, timeout=10) as resp: - if resp.status !=200: - await setup() - resp = await session.get("https://anilist.co/api/manga/search/" + searchText, params={'access_token':access_token}, timeout=10) - - - request = await resp.json() - closestManga = getListOfCloseManga(searchText, request) - fullMangaList = [] - - for manga in closestManga: - try: - async with session.get("https://anilist.co/api/manga/" + str(manga['id']) + "/staff", params={'access_token':access_token}, timeout=10) as fullManga: - if fullManga.status !=200: - await setup() - fullManga = await session.get("https://anilist.co/api/manga/" + str(manga['id']) + "/staff", params={'access_token':access_token}, timeout=10) - - fullMangaJson = await fullManga.json() - fullMangaList.append(fullMangaJson) - except: - pass - - potentialHits = [] - for manga in fullMangaList: - for staff in manga['staff']: - isRightName = True - fullStaffName = staff['name_first'] + ' ' + staff['name_last'] - authorNamesSplit = authorName.split(' ') - - for name in authorNamesSplit: - if not (name.lower() in fullStaffName.lower()): - isRightName = False - - if isRightName: - potentialHits.append(manga) - - if potentialHits: - return getClosestManga(searchText, potentialHits) - - return None - - except Exception as e: - traceback.print_exc() - return None - -async def getLightNovelDetails(searchText): - return await getMangaDetails(searchText, True) - -#Returns the closest manga series given a specific search term -async def getMangaDetails(searchText, isLN=False): - cachedAnime = DatabaseHandler.checkForMalEntry('anilistmanga', searchText, isLN) - if cachedAnime is not None: - if cachedAnime['update']: - print("found cached anime, needs update in anilist") - return await getMangaDetailsById(cachedAnime['id']) - else: - print("found cached anime, doesn't need update in anilist") - return cachedAnime['content'] - try: - async with session.get("https://anilist.co/api/manga/search/" + searchText, params={'access_token':access_token}, timeout=10) as resp: - if resp.status != 200: - await setup() - resp = await session.get("https://anilist.co/api/manga/search/" + searchText, params={'access_token':access_token}, timeout=10) - - request = await resp.json() - closestManga = getClosestManga(searchText, request, isLN) - - if (closestManga is not None): - response = await session.get("https://anilist.co/api/manga/" + str(closestManga['id']), params={'access_token':access_token}, timeout=10) - json = await response.json() - - json['genres'] = [genre for genre in json['genres'] if genre] - json['synonyms'] = [synonym for synonym in json['synonyms'] if synonym] - - return json - else: - return None - - except Exception as e: - print("Error finding manga:{} in anilist.\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - -#Returns the closest manga series given an id -async def getMangaDetailsById(mangaId): - try: - async with session.get("https://anilist.co/api/manga/" + str(mangaId), params={'access_token':access_token}, timeout=10) as resp: - request = await resp.json() - return request - except Exception as e: - - return None - -#Used to determine the closest manga to a given search term in a list -def getListOfCloseManga(searchText, mangaList): - try: - ratio = 0.90 - returnList = [] - - for manga in mangaList: - alreadyExists = False - for thing in returnList: - if int(manga['id']) == int(thing['id']): - alreadyExists = True - break - if (alreadyExists): - continue - - if round(difflib.SequenceMatcher(lambda x: x == "", manga['title_english'].lower(), searchText.lower()).ratio(), 3) >= ratio: - returnList.append(manga) - elif round(difflib.SequenceMatcher(lambda x: x == "", manga['title_romaji'].lower(), searchText.lower()).ratio(), 3) >= ratio: - returnList.append(manga) - elif not (manga['synonyms'] is None): - for synonym in manga['synonyms']: - if round(difflib.SequenceMatcher(lambda x: x == "", synonym.lower(), searchText.lower()).ratio(), 3) >= ratio: - returnList.append(manga) - break - return returnList - except Exception as e: - traceback.print_exc() - return None - -#Used to determine the closest manga to a given search term in a list -def getClosestManga(searchText, mangaList, isLN=False): - try: - mangaNameList = [] - - for manga in mangaList: - if isLN and 'novel' not in manga['type'].lower(): - mangaList.remove(manga) - elif not isLN and 'novel' in manga['type'].lower(): - mangaList.remove(manga) - - for manga in mangaList: - if isLN and 'novel' not in manga['type'].lower(): - mangaList.remove(manga) - elif not isLN and 'novel' in manga['type'].lower(): - mangaList.remove(manga) - - for manga in mangaList: - mangaNameList.append(manga['title_english'].lower()) - mangaNameList.append(manga['title_romaji'].lower()) - - for synonym in manga['synonyms']: - mangaNameList.append(synonym.lower()) - - closestNameFromList = difflib.get_close_matches(searchText.lower(), mangaNameList, 1, 0.90)[0] - - for manga in mangaList: - if not ('one shot' in manga['type'].lower()): - if (manga['title_english'].lower() == closestNameFromList.lower()) or (manga['title_romaji'].lower() == closestNameFromList.lower()): - return manga - - for manga in mangaList: - for synonym in manga['synonyms']: - if synonym.lower() == closestNameFromList.lower(): - return manga - - return None - except Exception as e: - print("Error finding manga:{} in anilist.\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - -################################THESE ARE FOR POPULATING THE CACHE ##################################### -async def getGenres(medium): - try: - async with session.get("https://anilist.co/api/genre_list/".format(medium), params={'access_token':access_token}, timeout=10)as resp: - return await resp.json() - - except Exception as e: - print(e) - return None - -async def GetTop40ByGenre(medium, genre): - try: - async with session.get("https://anilist.co/api/browse/{}".format(medium), params={'access_token':access_token, 'genres':genre, 'sort':'popularity'}, timeout=10) as resp: - if resp.status != 200: - await setup() - resp = await session.get("https://anilist.co/api/manga/search/" + searchText, params={'access_token':access_token}, timeout=10) - if resp.status != 200: - print("Failed to get api info error code {}".format(resp.status)) - - request = await resp.json() - return request - except Exception as e: - print(e) - return None - -# Returns a json with the 40 anime from the 'page' of populartiy -async def get_page_by_popularity(medium, page): - try: - async with session.get("https://anilist.co/api/browse/{}".format(medium), params={'access_token':access_token, 'sort':'popularity-desc', 'page': page}, timeout=10) as resp: - if resp.status != 200: - await setup() - resp = await session.get("https://anilist.co/api/browse/{}".format(medium), params={'access_token':access_token, 'sort':'popularity', 'page': page}, timeout=10) - if resp.status != 200: - print("Failed to get ani-api info error code {}".format(resp.status)) - - request = await resp.json() - return request - pass - except Exception as e: - print(e) - return None - - -loop = asyncio.get_event_loop() -loop.run_until_complete(setup()) diff --git a/roboragi_old/AnimeBot.py b/roboragi_old/AnimeBot.py deleted file mode 100644 index 5d8d059..0000000 --- a/roboragi_old/AnimeBot.py +++ /dev/null @@ -1,367 +0,0 @@ -''' -AnimeBot.py -Acts as the "main" file and ties all the other functionality together. -''' - -import asyncio -import re -import traceback -import requests -import time - -import discord -import Discord -import DiscordoragiSearch -import CommentBuilder -import DatabaseHandler -import Config -import Reference - -#the servers where expanded requests are disabled -disableexpanded = [''] -async_queue = asyncio.Queue(maxsize = 32) -ownerID = '164546159140929538' - -@Discord.client.event -async def on_ready(): - print('Logged in as') - print(Discord.client.user.name) - print(Discord.client.user.id) - print('------') - -@Discord.client.event -async def on_server_join(server): - DatabaseHandler.addServerToDatabase(server.id) - print("Added server {} to database".format(server.id)) - -async def process_message(message, is_edit=False): - #Anime/Manga requests that are found go into separate arrays - animeArray = [] - mangaArray = [] - lnArray = [] - - #Checks if bot has permissions to embed - if message.channel.type != discord.ChannelType.private: - canEmbed = message.channel.server.default_role.permissions.embed_links - else: - canEmbed = True - if not canEmbed: - botMember = Discord.getMemberFromID(Config.clientid, message.server) - defaultroleperm = botMember.top_role.permissions - canEmbed = defaultroleperm.embed_links - - - isAdmin = message.author.top_role.permissions.administrator - isServerMod = message.author.top_role.permissions.manage_server - isOwner = message.author.id == ownerID - - if message.author.bot: - return - - - #ignores all "code" markup (i.e. anything between backticks) - preCleanMessage = re.sub(r"\`(.*?)\`", "", message.clean_content) - cleanMessage = re.sub(r'<:.+?:([0-9]{15,21})>', "", preCleanMessage) - messageReply = '' - - if re.search('({!help.*?}|{{!help.*?}}||<>)', cleanMessage, re.S) is not None: - try: - localEm = CommentBuilder.buildHelpEmbed() - await Discord.client.send_message(message.channel, embed = localEm) - return - except: - return - - if re.search('({!command.*?}|{{!command.*?}}||<>)', cleanMessage, re.S) is not None: - if 'toggleexpanded' in cleanMessage.lower() and (isAdmin or isServerMod): - try: - allowedStatus = DatabaseHandler.toggleAllowExpanded(message.server.id) - print("Toggled allowExpanded for server {}".format(message.server.id)) - if allowedStatus.lower() == 'true': - await Discord.client.send_message(message.channel, "Expanded requests are now allowed.") - else: - await Discord.client.send_message(message.channel, "Expanded requests are now disallowed.") - return - except Exception as e: - print(e) - return - - if 'addserver' in cleanMessage.lower() and (isOwner == True): - try: - DatabaseHandler.addServerToDatabase(message.server.id) - await Discord.client.send_message(message.channel, "Server has been added.") - return - except Exception as e: - print(e) - return - - else: - print("command failed, user probably has insufficient rights") - return - - - sender = re.search('[@]([A-Za-z0-9 _-]+?)(>|}|$)', cleanMessage, re.S) - mentionArray = message.raw_mentions - if re.search('({!stats.*?}|{{!stats.*?}}||<>)', cleanMessage, re.S) is not None and sender is not None: - for mention in mentionArray: - if not canEmbed: - messageReply = CommentBuilder.buildStatsComment(server=message.server, username=mention) - else: - localEm = CommentBuilder.buildStatsEmbed(server=message.server, username=mention) - await Discord.client.send_message(message.channel, embed=localEm) - return None - if re.search('({!sstats}|{{!sstats}}||<>)', cleanMessage, re.S) is not None: - if not canEmbed: - messageReply = CommentBuilder.buildStatsComment(server = message.server) - else: - localEm = CommentBuilder.buildStatsEmbed(server = message.server) - await Discord.client.send_message(message.channel, embed=localEm) - return None - elif re.search('({!stats.*?}|{{!stats.*?}}||<>)', cleanMessage, re.S) is not None: - if not canEmbed: - messageReply = CommentBuilder.buildStatsComment() - else: - localEm = CommentBuilder.buildStatsEmbed() - await Discord.client.send_message(message.channel, embed=localEm) - return None - else: - - #The basic algorithm here is: - #If it's an expanded request, build a reply using the data in the braces, clear the arrays, add the reply to the relevant array and ignore everything else. - #If it's a normal request, build a reply using the data in the braces, add the reply to the relevant array. - - #Counts the number of expanded results vs total results. If it's not just a single expanded result, they all get turned into normal requests. - numOfRequest = 0 - numOfExpandedRequest = 0 - forceNormal = False - expandedAllowed = DatabaseHandler.checkServerConfig('allowexpanded', message.server.id) - if expandedAllowed == False: - forceNormal = True - for match in re.finditer("\{{2}([^}]*)\}{2}|\<{2}([^>]*)\>{2}", cleanMessage, re.S): - numOfRequest += 1 - numOfExpandedRequest += 1 - print("Request found: {}".format(match.group(0))) - - for match in re.finditer("(?<=(?]*)(?=\>(?!\>))", cleanMessage, re.S): - numOfRequest += 1 - print("Request found: {}".format(match.group(0))) - - if (numOfExpandedRequest >= 1) and (numOfRequest > 1): - forceNormal = True - - #if numOfRequest != 0: - #await Discord.client.send_typing(message.channel) - #Expanded Anime - for match in re.finditer("\{{2}([^}]*)\}{2}", cleanMessage, re.S): - reply = '' - if match.group(1) != '': - if (forceNormal) or (str(message.channel).lower() in disableexpanded): - reply = await DiscordoragiSearch.buildAnimeReply(match.group(1), message, False, canEmbed) - else: - reply = await DiscordoragiSearch.buildAnimeReply(match.group(1), message, True, canEmbed) - - if (reply is not None): - animeArray.append(reply) - else: - print("Empty request, ignoring") - - #Normal Anime - for match in re.finditer("(?<=(?]*)\>{2}(?!(:|\>))", cleanMessage, re.S): - if match.group(1) != '': - reply = '' - - if (forceNormal) or (str(message.channel).lower() in disableexpanded): - reply = await DiscordoragiSearch.buildMangaReply(match.group(1), message, False, canEmbed) - else: - reply = await DiscordoragiSearch.buildMangaReply(match.group(1), message, True, canEmbed) - - if (reply is not None): - mangaArray.append(reply) - else: - print("Empty request, ignoring") - - #AUTHOR SEARCH EXPANDED - for match in re.finditer("\<{2}([^>]*)\>{2}:\(([^)]+)\)", cleanMessage, re.S): - if match.group(1) != '': - reply = '' - - if (forceNormal) or (str(message.server).lower() in disableexpanded): - reply = await DiscordoragiSearch.buildMangaReplyWithAuthor(match.group(1), match.group(2), message, False, canEmbed) - else: - reply = await DiscordoragiSearch.buildMangaReplyWithAuthor(match.group(1), match.group(2), message, True, canEmbed) - - if (reply is not None): - mangaArray.append(reply) - else: - print("Empty request, ignoring") - - #Normal Manga - #NORMAL - for match in re.finditer("(?<=(?]+)\>(?!(:|\>))", cleanMessage, re.S): - if match.group(1) != '': - reply = await DiscordoragiSearch.buildMangaReply(match.group(1), message, False, canEmbed) - - if (reply is not None): - mangaArray.append(reply) - else: - print("Empty request, ignoring") - - #AUTHOR SEARCH - for match in re.finditer("(?<=(?]*)\>:\(([^)]+)\)", cleanMessage, re.S): - reply = await DiscordoragiSearch.buildMangaReplyWithAuthor(match.group(1), match.group(2), message, False, canEmbed) - - if (reply is not None): - mangaArray.append(reply) - - #Expanded LN - for match in re.finditer("\]{2}([^]]*)\[{2}", cleanMessage, re.S): - if match.group(1) != '': - reply = '' - - if (forceNormal) or (str(message.server).lower() in disableexpanded): - reply = await DiscordoragiSearch.buildLightNovelReply(match.group(1), False, message, canEmbed) - else: - reply = await DiscordoragiSearch.buildLightNovelReply(match.group(1), True, message, canEmbed) - - if (reply is not None): - lnArray.append(reply) - else: - print("Empty request, ignoring") - - #Normal LN - for match in re.finditer("(?<=(?', then recombine them - for i, animeReply in enumerate(animeArray): - if not (i is 0): - messageReply += '\n\n' - if not (animeReply['title'] in postedAnimeTitles): - postedAnimeTitles.append(animeReply['title']) - if not canEmbed: - messageReply += animeReply['comment'] - else: - messageReply = 'n/a' - if mangaArray: - messageReply += '\n\n' - #Adding all the manga to the final message - for i, mangaReply in enumerate(mangaArray): - if not (i is 0): - messageReply += '\n\n' - if not (mangaReply['title'] in postedMangaTitles): - postedMangaTitles.append(mangaReply['title']) - if not canEmbed: - messageReply += mangaReply['comment'] - else: - messageReply = 'n/a' - if lnArray: - messageReply += '\n\n' - #Adding all the manga to the final comment - for i, lnReply in enumerate(lnArray): - if not (i is 0): - commentReply += '\n\n' - - if not (lnReply['title'] in postedLNTitles): - postedLNTitles.append(lnReply['title']) - if not canEmbed: - messageReply += lnReply['comment'] - else: - messageReply = 'N/A' - #If there are more than 10 requests, shorten them all - if not (messageReply is '') and (len(animeArray) + len(mangaArray) >= 10): - messageReply = re.sub(r"\^\((.*?)\)", "", messageReply, flags=re.M) - #If there was actually something found, add the signature and post the message to Reddit. Then, add the message to the "already seen" database. - if not (messageReply is ''): - - if is_edit: - if not canEmbed: - await Discord.client.send_message(message.channel, messageReply) - else: - for i, animeReply in enumerate(animeArray): - await Discord.client.send_message(message.channel, embed=animeReply['embed']) - for i, mangaReply in enumerate(mangaArray): - await Discord.client.send_message(message.channel, embed=mangaReply['embed']) - for i, lnReply in enumerate(lnArray): - await Discord.client.send_message(message.channel, embed=lnReply['embed']) - else: - try: - print("Message created.\n") - if not canEmbed: - await Discord.client.send_message(message.channel, messageReply) - else: - for i, animeReply in enumerate(animeArray): - await Discord.client.send_message(message.channel, embed=animeReply['embed']) - for i, mangaReply in enumerate(mangaArray): - await Discord.client.send_message(message.channel, embed=mangaReply['embed']) - for i, lnReply in enumerate(lnArray): - await Discord.client.send_message(message.channel, embed=lnReply['embed']) - except discord.errors.Forbidden: - print('Request from banned channel: ' + str(message.channel) + '\n') - except Exception as e: - print(e) - traceback.print_exc() - except: - traceback.print_exc() - else: - try: - if is_edit: - return None - else: - DatabaseHandler.addMessage(message.id, message.author.id, message.server.id, False) - except: - traceback.print_exc() - -#Overwrite on_message so we can run our stuff -@Discord.client.event -async def on_message(message): - from DiscordoragiSearch import isValidMessage #local import here to fix attribute not found error - print('Message recieved') - #Is the message valid (i.e. it's not made by Discordoragi and I haven't seen it already). If no, try to add it to the "already seen pile" and skip to the next message. If yes, keep going. - if not (isValidMessage(message)): - try: - if not (DatabaseHandler.messageExists(message.id)): - DatabaseHandler.addMessage(message.id, message.author.id, message.server.id, False) - except Exception: - traceback.print_exc() - pass - else: - await process_message(message) - -# ------------------------------------# -#Here's the stuff that actually gets run - -#Initialise Discord. -print('Starting Bot') -Discord.run() diff --git a/roboragi_old/AnimePlanet.py b/roboragi_old/AnimePlanet.py deleted file mode 100644 index 4136664..0000000 --- a/roboragi_old/AnimePlanet.py +++ /dev/null @@ -1,112 +0,0 @@ -from pyquery import PyQuery as pq -import aiohttp -import difflib -import traceback -import pprint -import collections - -BASE_URL = "http://www.anime-planet.com" - -session = aiohttp.ClientSession() - -def sanitiseSearchText(searchText): - return searchText.replace('(TV)', 'TV') - -async def getAnimeURL(searchText): - try: - searchText = sanitiseSearchText(searchText) - - async with session.get(BASE_URL + "/anime/all?name=" + searchText.replace(" ", "%20"), timeout=10) as resp: - html = await resp.text() - ap = pq(html) - animeList = [] - - #If it's taken us to the search page - if ap.find('.cardDeck.pure-g.cd-narrow[data-type="anime"]'): - for entry in ap.find('.card.pure-1-6'): - entryTitle = pq(entry).find('h4').text() - entryURL = pq(entry).find('a').attr('href') - - anime = {} - anime['title'] = entryTitle - anime['url'] = BASE_URL + entryURL - animeList.append(anime) - - closestName = difflib.get_close_matches(searchText.lower(), [x['title'].lower() for x in animeList], 1, 0.85)[0] - closestURL = '' - - for anime in animeList: - if anime['title'].lower() == closestName: - return anime['url'] - - #Else if it's taken us right to the series page, get the url from the meta tag - else: - return ap.find("meta[property='og:url']").attr('content') - return None - - except Exception as e: - return None - -#Probably doesn't need to be split into two functions given how similar they are, but it might be worth keeping separate for the sake of issues between anime/manga down the line -async def getMangaURL(searchText, authorName=None): - try: - if authorName: - async with sessions.get(BASE_URL + "/manga/all?name=" + searchText.replace(" ", "%20") + '&author=' + authorName.replace(" ", "%20"), timeout=10) as resp: - html = await resp.text() - if "No results found" in html: - rearrangedAuthorNames = collections.deque(authorName.split(' ')) - rearrangedAuthorNames.rotate(-1) - rearrangedName = ' '.join(rearrangedAuthorNames) - async with session.get(BASE_URL + "/manga/all?name=" + searchText.replace(" ", "%20") + '&author=' + rearrangedName.replace(" ", "%20"), timeout=10) as resp: - html = await resp.text() - - else: - async with session.get(BASE_URL + "/manga/all?name=" + searchText.replace(" ", "%20"), timeout=10) as resp: - html = await resp.text() - - ap = pq(html) - - mangaList = [] - - #If it's taken us to the search page - if ap.find('.cardDeck.pure-g.cd-narrow[data-type="manga"]'): - for entry in ap.find('.card.pure-1-6'): - entryTitle = pq(entry).find('h4').text() - entryURL = pq(entry).find('a').attr('href') - - manga = {} - manga['title'] = entryTitle - manga['url'] = BASE_URL + entryURL - mangaList.append(manga) - - if authorName: - authorName = authorName.lower() - authorName = authorName.split(' ') - - for manga in mangaList: - manga['title'] = manga['title'].lower() - - for name in authorName: - manga['title'] = manga['title'].replace(name, '') - manga['title'] = manga['title'].replace('(', '').replace(')', '').strip() - - closestName = difflib.get_close_matches(searchText.lower(), [x['title'].lower() for x in mangaList], 1, 0.85)[0] - closestURL = '' - - for manga in mangaList: - if manga['title'].lower() == closestName: - return manga['url'] - - #Else if it's taken us right to the series page, get the url from the meta tag - else: - return ap.find("meta[property='og:url']").attr('content') - return None - - except: - return None - -def getAnimeURLById(animeId): - return 'http://www.anime-planet.com/anime/' + str(animeId) - -def getMangaURLById(mangaId): - return 'http://www.anime-planet.com/manga/' + str(mangaId) \ No newline at end of file diff --git a/roboragi_old/CommentBuilder.py b/roboragi_old/CommentBuilder.py deleted file mode 100644 index dc3a43f..0000000 --- a/roboragi_old/CommentBuilder.py +++ /dev/null @@ -1,1407 +0,0 @@ -''' -CommentBuilder.py -Takes the data given to it by search and formats it into a comment -''' - -import re -from os import linesep -from discord import Embed -import traceback - -import DatabaseHandler -import pprint -import Discord - -#Removes the (Source: MAL) or (Written by X) bits from the decriptions in the databases -def cleanupDescription(desc): - for match in re.finditer("([\[\<\(](.*?)[\]\>\)])", desc, re.S): - if 'ource' in match.group(1).lower(): - desc = desc.replace(match.group(1), '') - if 'MAL' in match.group(1): - desc = desc.replace(match.group(1), '') - - for match in re.finditer("([\<](.*?)[\>])", desc, re.S): - if 'br' in match.group(1).lower(): - desc = desc.replace(match.group(1), '') - - reply = '' - for i, line in enumerate(linesep.join([s for s in desc.splitlines() if s]).splitlines()): - if i is not 0: - reply += '\n' - reply += line + '\n' - return reply - -#Builds an anime comment from MAL/Anilist data -def buildAnimeComment(isExpanded, mal, ani, ap, anidb): - try: - comment = '' - - title = None - jTitle = None - - cType = None - - malURL = None - aniURL = None - apURL = ap - anidbURL = anidb - - youtubeTrailer = None - - status = None - episodes = None - genres = [] - - countdown = None - nextEpisode = None - - desc = None - - if mal: - desc = mal['synopsis'] - - if mal['type']: - cType = mal['type'] - - malURL = 'http://myanimelist.net/anime/' + str(mal['id']) - - if ani is not None: - title = ani['title_romaji'] - aniURL = 'http://anilist.co/anime/' + str(ani['id']) - - try: - cType = ani['type'] - desc = ani['description'] - except: - pass - - status = ani['airing_status'].title() - - try: - if ani['title_japanese'] is not None: - jTitle = ani['title_japanese'] - - if ani['youtube_id'] is not None: - youtubeTrailer = ani['youtube_id'] - - if ani['total_episodes'] is not None: - if ani['total_episodes'] == 0: - episodes = 'Unknown' - else: - episodes = ani['total_episodes'] - - if ani['genres'] is not None: - genres = ani['genres'] - - if ani['airing'] is not None: - countdown = ani['airing']['countdown'] - nextEpisode = ani['airing']['next_episode'] - except: - print('No full details for Anilist') - - stats = DatabaseHandler.getRequestStats(title, 'Anime') - - if ani is not None: - stats = DatabaseHandler.getRequestStats(ani['title_romaji'],'Anime') - - #---------- BUILDING THE COMMENT ----------# - - #----- TITLE -----# - comment += '**' + title.strip() + '** - \n\n' - - #----- LINKS -----# - urlComments = [] - - if malURL is not None: - urlComments.append(malURL) - if apURL is not None: - urlComments.append(apURL) - if ani is not None: - urlComments.append(aniURL) - if anidbURL is not None: - urlComments.append(anidbURL) - - for i, link in enumerate(urlComments): - if i is not 0: - comment += '\n\n' - comment += link - - #----- JAPANESE TITLE -----# - if (isExpanded): - if jTitle is not None: - comment += '\n\n' - - splitJTitle = jTitle.split() - for i, word in enumerate(splitJTitle): - if not (i == 0): - comment += ' ' - comment += word - - #----- INFO LINE -----# - if (isExpanded): - comment += '\n\n(' - - if cType: - comment += '**' + cType + '** | ' - - comment += '**Status:** ' + status - - if cType != 'Movie': - comment += ' | **Episodes:** ' + str(episodes) - - comment += ' | **Genres:** ' - else: - comment += '\n\n(' - - if cType: - comment += cType + ' | ' - - comment += 'Status: ' + status - - if cType != 'Movie': - comment += ' | Episodes: ' + str(episodes) - - comment += ' | Genres: ' - - if not (genres == []): - for i, genre in enumerate(genres): - if i is not 0: - comment += ', ' - comment += genre - else: - comment += 'None' - - if (isExpanded) and (stats is not None): - comment += ' \n**Stats:** ' + str(stats['total']) + ' requests across ' + str(stats['uniqueSubreddits']) + ' server(s)) - ' + str(round(stats['totalAsPercentage'],3)) + '% of all requests' - else: - comment += ')' - - #----- EPISODE COUNTDOWN -----# - if (countdown is not None) and (nextEpisode is not None): - #countdown is given to us in seconds - days, countdown = divmod(countdown, 24*60*60) - hours, countdown = divmod(countdown, 60*60) - minutes, countdown = divmod(countdown, 60) - - comment += '\n\n(Episode ' + str(nextEpisode) + ' airs in ' + str(days) + ' days, ' + str(hours) + ' hours, ' + str(minutes) + ' minutes)' - - #----- DESCRIPTION -----# - if (isExpanded): - comment += '\n\n' + cleanupDescription(desc) - - #----- END -----# - receipt = '(A) Request successful: ' + title + ' - ' - if malURL is not None: - receipt += 'MAL ' - if apURL is not None: - receipt += 'AP ' - if ani is not None: - receipt += 'ANI ' - if anidbURL is not None: - receipt += 'ADB ' - print(receipt.encode('utf8')) - - #We return the title/comment separately so we can track if multiples of the same comment have been requests (e.g. {Nisekoi}{Nisekoi}{Nisekoi}) - dictToReturn = {} - dictToReturn['title'] = title - dictToReturn['comment'] = comment - - return dictToReturn - except Exception as e: - print("Error creating embed: {}\n".format(e)) - #traceback.print_exc() - return None - -#Builds a manga comment from MAL/Anilist/MangaUpdates data -def buildMangaComment(isExpanded, mal, ani, mu, ap): - try: - comment = '' - - title = None - jTitle = None - - cType = None - - malURL = None - aniURL = None - muURL = mu - apURL = ap - - status = None - chapters = None - volumes = None - genres = [] - - desc = None - - if not (mal is None): - title = mal['title'] - malURL = 'http://myanimelist.net/manga/' + str(mal['id']) - desc = mal['synopsis'] - status = mal['status'] - - cType = mal['type'] - - try: - if (int(mal['chapters']) == 0): - chapters = 'Unknown' - else: - chapters = mal['chapters'] - except: - chapters = 'Unknown' - - try: - volumes = mal['volumes'] - except: - volumes = 'Unknown' - - if ani is not None: - if title is None: - title = ani['title_english'] - aniURL = 'http://anilist.co/manga/' + str(ani['id']) - desc = ani['description'] - status = ani['publishing_status'].title() - - cType = ani['type'] - - try: - if ani['title_japanese'] is not None: - jTitle = ani['title_japanese'] - - if ani['total_chapters'] is not None: - if ani['total_chapters'] == 0: - chapters = 'Unknown' - else: - chapters = ani['total_chapters'] - - if ani['total_volumes'] is not None: - volumes = ani['total_volumes'] - else: - volumes = 'Unknown' - - if ani['genres'] is not None: - genres = ani['genres'] - - except Exception as e: - print(e) - - stats = DatabaseHandler.getRequestStats(title,'Manga') - - #---------- BUILDING THE COMMENT ----------# - - #----- TITLE -----# - comment += '**' + title.strip() + '** - \n\n' - - #----- LINKS -----# - urlComments = [] - - if malURL is not None: - urlComments.append(malURL) - if apURL is not None: - urlComments.append(apURL) - if aniURL is not None: - urlComments.append(aniURL) - if muURL is not None: - urlComments.append(muURL) - - for i, link in enumerate(urlComments): - if i is not 0: - comment += '\n\n' - comment += link - - comment += '\n\n' - - #----- JAPANESE TITLE -----# - if (isExpanded): - if jTitle is not None: - comment += '\n\n' - - splitJTitle = jTitle.split() - for i, word in enumerate(splitJTitle): - if not (i == 0): - comment += ' ' - comment += word - - #----- INFO LINE -----# - - if (isExpanded): - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += '**' + cType + '** | ' - - comment += '**Status:** ' + status - - if (cType != 'Light Novel'): - if str(chapters) is not 'Unknown': - comment += ' | **Chapters:** ' + str(chapters) - else: - comment += ' | **Volumes:** ' + str(volumes) - - if genres: - comment += ' | **Genres:** ' - else: - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += cType + ' | ' - - comment += 'Status: ' + status - - if (cType != 'Light Novel'): - if str(chapters) is not 'Unknown': - comment += ' | Chapters: ' + str(chapters) - else: - comment += ' | Volumes: ' + str(volumes) - - if genres: - comment += ' | Genres: ' - - if genres: - for i, genre in enumerate(genres): - if i is not 0: - comment += ', ' - comment += genre - - if (isExpanded) and (stats is not None): - comment += ' \n**Stats:** ' + str(stats['total']) + ' requests across ' + str(stats['uniqueSubreddits']) + ' server(s)) - ' + str(round(stats['totalAsPercentage'],3)) + '% of all requests' - else: - comment += ')' - - #----- DESCRIPTION -----# - if (isExpanded): - comment += '\n\n' + cleanupDescription(desc) - - #----- END -----# - receipt = '(M) Request successful: ' + title + ' - ' - if malURL is not None: - receipt += 'MAL ' - if ap is not None: - receipt += 'AP ' - if ani is not None: - receipt += 'ANI ' - if muURL is not None: - receipt += 'MU ' - print(receipt.encode('utf8')) - - dictToReturn = {} - dictToReturn['title'] = title - dictToReturn['comment'] = comment - - return dictToReturn - except: - traceback.print_exc() - return None - -#Builds a manga comment from MAL/Anilist/MangaUpdates data -def buildLightNovelComment(isExpanded, mal, ani, nu, lndb): - try: - comment = '' - - title = None - jTitle = None - - cType = None - - malURL = None - aniURL = None - nuURL = nu - lndbURL = lndb - - status = None - chapters = None - volumes = None - genres = [] - - desc = None - - if not (mal is None): - title = mal['title'] - malURL = 'http://myanimelist.net/manga/' + str(mal['id']) - desc = mal['synopsis'] - status = mal['status'] - - cType = mal['type'] - - try: - if (int(mal['chapters']) == 0): - chapters = 'Unknown' - else: - chapters = mal['chapters'] - except: - chapters = 'Unknown' - - try: - volumes = mal['volumes'] - except: - volumes = 'Unknown' - - if ani is not None: - if title is None: - title = ani['title_english'] - aniURL = 'http://anilist.co/manga/' + str(ani['id']) - desc = ani['description'] - status = ani['publishing_status'].title() - - cType = ani['type'] - - try: - if ani['title_japanese'] is not None: - jTitle = ani['title_japanese'] - - if ani['total_chapters'] is not None: - if ani['total_chapters'] == 0: - chapters = 'Unknown' - else: - chapters = ani['total_chapters'] - - if ani['total_volumes'] is not None: - volumes = ani['total_volumes'] - else: - volumes = 'Unknown' - - if ani['genres'] is not None: - genres = ani['genres'] - - except Exception as e: - print(e) - - stats = DatabaseHandler.getRequestStats(title,'LN') - - #---------- BUILDING THE COMMENT ----------# - - #----- TITLE -----# - comment += '**' + title.strip() + '** -\n\n' - - #----- LINKS -----# - urlComments = [] - - if malURL is not None: - urlComments.append(malURL) - if aniURL is not None: - urlComments.append(aniURL) - if nuURL is not None: - urlComments.append(nuURL) - if lndbURL is not None: - urlComments.append(lndbURL) - - for i, link in enumerate(urlComments): - if i is not 0: - comment += '\n ' - comment += link - - comment += '\n\n' - - #----- JAPANESE TITLE -----# - if (isExpanded): - if jTitle is not None: - comment += '\n\n' - - splitJTitle = jTitle.split() - for i, word in enumerate(splitJTitle): - if not (i == 0): - comment += ' ' - comment += word - - #----- INFO LINE -----# - - if (isExpanded): - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += '**' + cType + '** | ' - - comment += '**Status:** ' + status - - if (cType != 'Light Novel'): - if str(chapters) is not 'Unknown': - comment += ' | **Chapters:** ' + str(chapters) - else: - comment += ' | **Volumes:** ' + str(volumes) - - if genres: - comment += ' | **Genres:** ' - else: - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += cType + ' | ' - - comment += 'Status: ' + status - - if (cType != 'Light Novel'): - if str(chapters) is not 'Unknown': - comment += ' | Chapters: ' + str(chapters) - else: - comment += ' | Volumes: ' + str(volumes) - - if genres: - comment += ' | Genres: ' - - if genres: - for i, genre in enumerate(genres): - if i is not 0: - comment += ', ' - comment += genre - - if (isExpanded) and (stats is not None): - comment += ' \n**Stats:** ' + str(stats['total']) + ' requests across ' + str(stats['uniqueSubreddits']) + ' subreddit(s)^) ^- ^' + str(round(stats['totalAsPercentage'],3)) + '% ^of ^all ^requests' - else: - comment += ')' - - #----- DESCRIPTION -----# - if (isExpanded): - comment += '\n\n' + cleanupDescription(desc) - - #----- END -----# - receipt = '(LN) Request successful: ' + title + ' - ' - if malURL is not None: - receipt += 'MAL ' - if ani is not None: - receipt += 'ANI ' - if nuURL is not None: - receipt += 'MU ' - if lndbURL is not None: - receipt += 'LNDB ' - print(receipt.encode('utf8')) - - dictToReturn = {} - dictToReturn['title'] = title - dictToReturn['comment'] = comment - - return dictToReturn - except: - traceback.print_exc() - return None - -#Builds a stats comment. If it is basic stats the default server id is the Discordoragi help server -def buildStatsComment(server=None, username=None, serverID="171004769069039616"): - try: - statComment = '' - receipt = '(S) Request successful: Stats' - - if username: - userStats = DatabaseHandler.getUserStats(username) - - if userStats: - statComment += 'Some stats on ' + username + ':\n\n' - statComment += '- **' + str(userStats['totalUserComments']) + '** total comments searched (' + str(round(userStats['totalUserCommentsAsPercentage'], 3)) + '% of all comments)\n' - statComment += '- **' + str(userStats['totalUserRequests']) + '** requests made (' + str(round(userStats['totalUserRequestsAsPercentage'], 3)) + '% of all requests and #' + str(userStats['overallRequestRank']) + ' overall)\n' - statComment += '- **' + str(userStats['uniqueRequests']) + '** unique anime/manga requested\n' - statComment += '- **/r/' + str(userStats['favouriteSubreddit']) + '** is their favourite server with ' + str(userStats['favouriteSubredditCount']) + ' requests (' + str(round(userStats['favouriteSubredditCountAsPercentage'], 3)) + '% of the server\'s requests)\n' - statComment += '\n' - statComment += 'Their most frequently requested anime/manga overall are:\n\n' - - for i, request in enumerate(userStats['topRequests']): - statComment += str(i + 1) + '. **' + str(request[0]) + '** (' + str(request[1]) + ' - ' + str(request[2]) + ' requests) \n' - else: - statComment += '/u/' + str(username) + ' hasn\'t used Roboragi yet.' - - receipt += ' - /u/' + username - elif server: - serverID = server.id - server = str(server) - serverStats = DatabaseHandler.getSubredditStats(server.lower()) - - if serverStats: - statComment += '**' + server +' Stats**\n\n' - - statComment += 'I\'ve searched through ' + str(serverStats['totalComments']) - statComment += ' unique comments on ' + server - statComment += ' and fulfilled a total of ' + str(serverStats['total']) + ' requests, ' - statComment += 'representing ' + str(round(serverStats['totalAsPercentage'], 2)) + '% of all requests. ' - statComment += 'A total of ' + str(serverStats['uniqueNames']) + ' unique anime/manga have been requested here, ' - statComment += 'with a mean value of ' + str(round(serverStats['meanValuePerRequest'], 3)) + ' requests/show' - statComment += ' and a standard deviation of ' + str(round(serverStats['standardDeviation'], 3)) + '.' - - statComment += '\n\n' - - statComment += 'The most frequently requested anime/manga on this server are:\n\n' - - for i, request in enumerate(serverStats['topRequests']): - statComment += str(i + 1) + '. **' + str(request[0]) + '** (' + str(request[1]) + ' - ' + str(request[2]) + ' requests)\n' - - statComment += '\n' - - statComment += 'The most frequent requesters on this server are:\n\n' - for i, requester in enumerate(serverStats['topRequesters']): - statComment += str(i + 1) + '. /u/' + str(requester[0]) + ' (' + str(requester[1]) + ' requests)\n' - - else: - statComment += 'There have been no requests on ' + str(server) + ' yet.' - - receipt += ' - ' + server - else: - basicStats = DatabaseHandler.getBasicStats(serverID) - - #The overall stats section - statComment += '**Overall Stats**\n\n' - - statComment += 'I\'ve searched through ' + str(basicStats['totalComments']) - statComment += ' unique comments and fulfilled a total of ' + str(basicStats['total']) - statComment += ' requests across ' + str(basicStats['uniqueSubreddits']) + ' unique server(s). ' - statComment += 'A total of ' + str(basicStats['uniqueNames']) - statComment += ' unique anime/manga have been requested, with a mean value of ' + str(round(basicStats['meanValuePerRequest'],3)) - statComment += ' requests/show and a standard deviation of ' + str(round(basicStats['standardDeviation'], 3)) + '.' - - statComment += '\n\n' - - statComment += 'The most frequently requested anime/manga overall are:\n\n' - - for i, request in enumerate(basicStats['topRequests']): - statComment += str(i + 1) + '. **' + str(request[0]) + '** (' + str(request[1]) + ' - ' + str(request[2]) + ' requests)\n' - - statComment += '\n' - - statComment += 'The most frequent requesters overall are: \n' - for i, requester in enumerate(basicStats['topRequesters']): - statComment += str(i + 1) + '. ' + str(Discord.getUsernameFromID(requester[0], )) + ' (' + str(requester[1]) + ' requests) \n' - - statComment += '\n' - receipt += ' - Basic' - - print(receipt.encode('utf8')) - return statComment - except: - traceback.print_exc() - return None - -# Builds an embed using the same data -def buildAnimeEmbed(isExpanded, mal, ani, ap, anidb): - try: - comment = '' - descComment = '' - title = None - jTitle = None - - cType = None - - malimage = '' - malURL = None - aniURL = None - apURL = ap - anidbURL = anidb - - youtubeTrailer = None - - status = None - episodes = None - genres = [] - - countdown = None - nextEpisode = None - - desc = None - - if mal: - desc = mal['synopsis'] - - if mal['type']: - cType = mal['type'] - - malURL = 'http://myanimelist.net/anime/' + str(mal['id']) - if mal['title']: - title = mal['title'] - - if mal['english']: - title = mal['english'] - - if mal['image']: - malimage = mal['image'] - - if mal['status']: - status = mal['status'] - if ani is not None: - title = ani['title_romaji'] - aniURL = 'http://anilist.co/anime/' + str(ani['id']) - - try: - cType = ani['type'] - desc = ani['description'] - except: - pass - - if status is None: - try: - status = ani['airing_status'].title() - except Exception as e: - print(e) - try: - if ani['title_japanese'] is not None: - jTitle = ani['title_japanese'] - - if ani['youtube_id'] is not None: - youtubeTrailer = ani['youtube_id'] - - if ani['total_episodes'] is not None: - if ani['total_episodes'] == 0: - episodes = 'Unknown' - else: - episodes = ani['total_episodes'] - - if ani['genres'] is not None: - genres = ani['genres'] - - if ani['airing'] is not None: - countdown = ani['airing']['countdown'] - nextEpisode = ani['airing']['next_episode'] - except: - print('No full details for Anilist') - - stats = DatabaseHandler.getRequestStats(title, 'Anime') - - if ani is not None: - stats = DatabaseHandler.getRequestStats(ani['title_romaji'],'Anime') - - #---------- BUILDING THE COMMENT ----------# - - comment = '' - - #----- LINKS -----# - urlComments = [] - allLinks = '' - - try: - mal_english = mal['english'] - except: - pass - - - if malURL is not None: - urlComments.append("[MAL]({})".format(sanitise_url_for_markdown(malURL))) - if apURL is not None: - urlComments.append("[AP]({})".format(sanitise_url_for_markdown(apURL))) - if ani is not None: - urlComments.append("[AL]({})".format(sanitise_url_for_markdown(aniURL))) - if anidbURL is not None: - urlComments.append("[AniDB]({})".format(sanitise_url_for_markdown(anidbURL))) - - for i, link in enumerate(urlComments): - if i is not 0: - allLinks += ', ' - allLinks += link - #----- JAPANESE TITLE -----# - if (isExpanded): - if jTitle is not None: - comment += '\n\n' - - splitJTitle = jTitle.split() - for i, word in enumerate(splitJTitle): - if not (i == 0): - comment += ' ' - comment += word - - #----- INFO LINE -----# - if (isExpanded): - comment += '\n\n(' - - if cType: - comment += '**' + cType + '** | ' - - comment += '**Status:** ' + status - - if cType != 'Movie': - comment += ' | **Episodes:** ' + str(episodes) - - comment += ' | **Genres:** ' - else: - comment += '\n\n(' - - if cType: - comment += cType + ' | ' - - comment += 'Status: ' + status - - if cType != 'Movie': - comment += ' | Episodes: ' + str(episodes) - - comment += ' | Genres: ' - - if not (genres == []): - for i, genre in enumerate(genres): - if i is not 0: - comment += ', ' - comment += genre - else: - comment += 'None' - - if (isExpanded) and (stats is not None): - comment += ') \n\n**Stats:** ' + str(stats['total']) + ' requests across ' + str(stats['uniqueSubreddits']) + ' server(s)) - ' + str(round(stats['totalAsPercentage'],3)) + '% of all requests' - else: - comment += ')' - - #----- EPISODE COUNTDOWN -----# - if (countdown is not None) and (nextEpisode is not None): - #countdown is given to us in seconds - days, countdown = divmod(countdown, 24*60*60) - hours, countdown = divmod(countdown, 60*60) - minutes, countdown = divmod(countdown, 60) - - comment += '\n\n(Episode ' + str(nextEpisode) + ' airs in ' + str(days) + ' days, ' + str(hours) + ' hours, ' + str(minutes) + ' minutes)' - - #----- DESCRIPTION -----# - if (isExpanded): - descComment += cleanupDescription(desc) - - #----- END -----# - receipt = '(A) Request successful: ' + title + ' - ' - if malURL is not None: - receipt += 'MAL ' - if apURL is not None: - receipt += 'AP ' - if ani is not None: - receipt += 'AL ' - if anidbURL is not None: - receipt += 'ADB ' - print(receipt.encode('utf8')) - try: - embed = buildEmbedObject(title, allLinks, comment, malimage, isExpanded, descComment) - except Exception as e: - print(e) - #We return the title/comment separately so we can track if multiples of the same comment have been requests (e.g. {Nisekoi}{Nisekoi}{Nisekoi}) - dictToReturn = {} - dictToReturn['title'] = title - dictToReturn['embed'] = embed - return dictToReturn - except Exception as e: - print(e) - #traceback.print_exc() - return None - -#sets up the embed for Mangas -def buildMangaEmbed(isExpanded, mal, ani, mu, ap): - try: - comment = '' - descComment = '' - - title = None - jTitle = None - - cType = None - - malimage = '' - malURL = None - aniURL = None - muURL = mu - apURL = ap - - status = None - chapters = None - volumes = None - genres = [] - - desc = None - - if not (mal is None): - title = mal['title'] - malURL = 'http://myanimelist.net/manga/' + str(mal['id']) - desc = mal['synopsis'] - status = mal['status'] - malimage = mal['image'] - - cType = mal['type'] - - try: - if (int(mal['chapters']) == 0): - chapters = 'Unknown' - else: - chapters = mal['chapters'] - except: - chapters = 'Unknown' - - try: - if (int(mal['volumes']) == 0): - volumes = 'Unknown' - else: - volumes = mal['volumes'] - except: - volumes = 'Unknown' - - if ani is not None: - if title is None: - title = ani['title_english'] - aniURL = 'http://anilist.co/manga/' + str(ani['id']) - if ani['description']: - desc = ani['description'] - - try: - status = ani['publishing_status'].title() - except: - pass - - cType = ani['type'] - - try: - if ani['title_japanese'] is not None: - jTitle = ani['title_japanese'] - - if ani['total_chapters'] is not None: - if ani['total_chapters'] == 0: - chapters = 'Unknown' - else: - chapters = ani['total_chapters'] - - if ani['total_volumes'] is not None: - if ani['total_volumes'] == 0: - volumes = 'Unknown' - else: - volumes = ani['total_volumes'] - - if ani['genres'] is not None: - genres = ani['genres'] - - except Exception as e: - print(e) - - stats = DatabaseHandler.getRequestStats(title,'Manga') - - #---------- BUILDING THE COMMENT ----------# - - #----- LINKS -----# - urlComments = [] - allLinks = '' - if malURL is not None: - urlComments.append("[MAL]({})".format(sanitise_url_for_markdown(malURL))) - if aniURL is not None: - urlComments.append("[ANI]({})".format(sanitise_url_for_markdown(aniURL))) - if apURL is not None: - urlComments.append("[AP]({})".format(sanitise_url_for_markdown(apURL))) - if muURL is not None: - urlComments.append("[MU]({})".format(sanitise_url_for_markdown(muURL))) - - for i, link in enumerate(urlComments): - if i is not 0: - allLinks += ', ' - allLinks += link - - #----- JAPANESE TITLE -----# - if (isExpanded): - if jTitle is not None: - comment += '\n\n' - - splitJTitle = jTitle.split() - for i, word in enumerate(splitJTitle): - if not (i == 0): - comment += ' ' - comment += word - - #----- INFO LINE -----# - - if (isExpanded): - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += '**' + cType + '** | ' - - comment += '**Status:** ' + status - - if (cType != 'Light Novel'): - if str(volumes) is not 'Unknown': - comment += ' | **Volumes:** ' + str(volumes) - if str(chapters) is not 'Unknown': - comment += ' | **Chapters:** ' + str(chapters) - else: - if str(volumes) is not 'Unknown': - comment += ' | **Volumes:** ' + str(volumes) - - if genres: - comment += ' | **Genres:** ' - else: - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += cType + ' | ' - - comment += 'Status: ' + status - - if (cType != 'Light Novel'): - if str(volumes) is not 'Unknown': - comment += ' | Volumes: ' + str(volumes) - if str(chapters) is not 'Unknown': - comment += ' | Chapters: ' + str(chapters) - else: - if str(volumes) is not 'Unknown': - comment += ' | Volumes: ' + str(volumes) - - if genres: - comment += ' | Genres: ' - - if genres: - for i, genre in enumerate(genres): - if i is not 0: - comment += ', ' - comment += genre - - if (isExpanded) and (stats is not None): - comment += ') \n\n**Stats:** ' + str(stats['total']) + ' requests across ' + str(stats['uniqueSubreddits']) + ' server(s)) - ' + str(round(stats['totalAsPercentage'],3)) + '% of all requests' - else: - comment += ')' - - #----- DESCRIPTION -----# - if (isExpanded): - descComment += cleanupDescription(desc) - - #----- END -----# - receipt = '(M) Request successful: ' + title + ' - ' - if malURL is not None: - receipt += 'MAL ' - if ap is not None: - receipt += 'AP ' - if ani is not None: - receipt += 'AL ' - if muURL is not None: - receipt += 'MU ' - print(receipt.encode('utf8')) - - #----- Build embed object -----# - try: - embed = buildEmbedObject(title, allLinks, comment, malimage, isExpanded, descComment) - except Exception as e: - print(e) - dictToReturn = {} - dictToReturn['title'] = title - dictToReturn['embed'] = embed - - return dictToReturn - except Exception as e: - print(e) - #traceback.print_exc() - return None - -#sets up the embed for Light Novels -def buildLightNovelEmbed(isExpanded, mal, ani, nu, lndb): - try: - comment = '' - descComment= '' - - title = None - jTitle = None - - cType = None - - malimage = '' - malURL = None - aniURL = None - nuURL = nu - lndbURL = lndb - - status = None - chapters = None - volumes = None - genres = [] - - desc = None - - if not (mal is None): - title = mal['title'] - malURL = 'http://myanimelist.net/manga/' + str(mal['id']) - desc = mal['synopsis'] - status = mal['status'] - malimage = mal['image'] - - cType = mal['type'] - - try: - if (int(mal['chapters']) == 0): - chapters = 'Unknown' - else: - chapters = mal['chapters'] - except: - chapters = 'Unknown' - - try: - if (int(mal['volumes']) == 0): - volumes = 'Unknown' - else: - volumes = mal['volumes'] - except: - volumes = 'Unknown' - - if ani is not None: - if title is None: - title = ani['title_english'] - aniURL = 'http://anilist.co/manga/' + str(ani['id']) - if ani['description']: - desc = ani['description'] - try: - status = ani['publishing_status'].title() - except: - pass - - cType = ani['type'] - - try: - if ani['title_japanese'] is not None: - jTitle = ani['title_japanese'] - - if ani['total_chapters'] is not None: - if ani['total_chapters'] == 0: - chapters = 'Unknown' - else: - chapters = ani['total_chapters'] - else: - volumes = 'Unknown' - - if ani['total_volumes'] is not None: - if ani['total_volumes'] == 0: - volumes = 'Unknown' - else: - volumes = ani['total_volumes'] - else: - volumes = 'Unknown' - - if ani['genres'] is not None: - genres = ani['genres'] - - except Exception as e: - print(e) - - stats = DatabaseHandler.getRequestStats(title,'LN') - - #---------- BUILDING THE COMMENT ----------# - - #----- LINKS -----# - urlComments = [] - allLinks = '' - if malURL is not None: - urlComments.append("[MAL]({})".format(sanitise_url_for_markdown(malURL))) - if aniURL is not None: - urlComments.append("[ANI]({})".format(sanitise_url_for_markdown(aniURL))) - if nuURL is not None: - urlComments.append("[NU]({})".format(sanitise_url_for_markdown(nuURL))) - if lndbURL is not None: - urlComments.append("[LNDB]({})".format(sanitise_url_for_markdown(lndbURL))) - - for i, link in enumerate(urlComments): - if i is not 0: - allLinks += ', ' - allLinks += link - - #----- JAPANESE TITLE -----# - if (isExpanded): - if jTitle is not None: - comment += '\n\n' - - splitJTitle = jTitle.split() - for i, word in enumerate(splitJTitle): - if not (i == 0): - comment += ' ' - comment += word - - #----- INFO LINE -----# - - if (isExpanded): - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += '**' + cType + '** | ' - - comment += '**Status:** ' + status - - if (cType != 'Light Novel'): - if str(volumes) is not 'Unknown': - comment += ' | **Volumes:** ' + str(volumes) - if str(chapters) is not 'Unknown': - comment += ' | **Chapters:** ' + str(chapters) - else: - if str(volumes) is not 'Unknown': - comment += ' | **Volumes:** ' + str(volumes) - - if genres: - comment += ' | **Genres:** ' - else: - comment += '\n\n(' - - if cType: - if cType == 'Novel': - cType = 'Light Novel' - - comment += cType + ' | ' - - comment += 'Status: ' + status - - if (cType != 'Light Novel'): - if str(chapters) is not 'Unknown': - comment += ' | Chapters: ' + str(chapters) - else: - comment += ' | Volumes: ' + str(volumes) - - if genres: - comment += ' | Genres: ' - - if genres: - for i, genre in enumerate(genres): - if i is not 0: - comment += ', ' - comment += genre - - if (isExpanded) and (stats is not None): - comment += ')\n\n**Stats:** ' + str(stats['total']) + ' requests across ' + str(stats['uniqueSubreddits']) + ' server(s)) - ' + str(round(stats['totalAsPercentage'],3)) + '% of all requests' - else: - comment += ')' - - #----- DESCRIPTION -----# - if (isExpanded): - descComment += cleanupDescription(desc) - - #----- END -----# - receipt = '(LN) Request successful: ' + title + ' - ' - if malURL is not None: - receipt += 'MAL ' - if ani is not None: - receipt += 'AL ' - if nuURL is not None: - receipt += 'MU ' - if lndbURL is not None: - receipt += 'LNDB ' - print(receipt.encode('utf8')) - - embed = buildEmbedObject(title, allLinks, comment, malimage, isExpanded, descComment) - - dictToReturn = {} - dictToReturn['title'] = title - dictToReturn['embed'] = embed - - return dictToReturn - except Exception as e: - print(e) - #traceback.print_exc() - return None - -def buildStatsEmbed(server=None, username=None, serverID="171004769069039616"): - try: - userNick = '' - statComment = '' - receipt = '(S) Request successful: Stats' - - if username is not None: - reqMember = server.get_member(username) - if reqMember.nick: - userNick = reqMember.nick - else: - userNick = reqMember.name - userStats = DatabaseHandler.getUserStats(username) - - if userStats: - statComment += 'Some stats on ' + userNick + ':\n\n' - statComment += '- **' + str(userStats['totalUserRequests']) + '** requests made (' + str(round(userStats['totalUserRequestsAsPercentage'], 3)) + '% of all requests and #' + str(userStats['overallRequestRank']) + ' overall)\n' - statComment += '- **' + str(userStats['uniqueRequests']) + '** unique anime/manga requested\n' - statComment += '\n' - statComment += 'Their most frequently requested anime/manga overall are:\n\n' - - for i, request in enumerate(userStats['topRequests']): - statComment += str(i + 1) + '. **' + str(request[0]) + '** (' + str(request[1]) + ' - ' + str(request[2]) + ' requests) \n' - else: - statComment += str(userNick) + ' hasn\'t used Roboragi yet.' - - receipt += ' - ' + userNick - elif server: - serverStats = DatabaseHandler.getSubredditStats(server) - - if serverStats: - statComment += '**' + server.name +' Stats**\n\n' - statComment += 'On ' + server.name - statComment += ' I have fulfilled a total of ' + str(serverStats['total']) + ' requests, ' - statComment += 'representing ' + str(round(serverStats['totalAsPercentage'], 2)) + '% of all requests. ' - statComment += 'A total of ' + str(serverStats['uniqueNames']) + ' unique anime/manga have been requested here, ' - statComment += 'with a mean value of ' + str(round(serverStats['meanValuePerRequest'], 3)) + ' requests/show' - statComment += ' and a standard deviation of ' + str(round(serverStats['standardDeviation'], 3)) + '.' - - statComment += '\n\n' - - statComment += 'The most frequently requested anime/manga on this server are:\n\n' - - for i, request in enumerate(serverStats['topRequests']): - statComment += str(i + 1) + '. **' + str(request[0]) + '** (' + str(request[1]) + ' - ' + str(request[2]) + ' requests)\n' - - else: - statComment += 'There have been no requests on ' + str(server) + ' yet.' - - receipt += ' - ' + server.name - else: - basicStats = DatabaseHandler.getBasicStats(serverID) - - #The overall stats section - statComment += '**Overall Stats**\n\n' - - statComment += 'I\'ve searched through ' + str(basicStats['totalComments']) - statComment += ' unique comments and fulfilled a total of ' + str(basicStats['total']) - statComment += ' requests across ' + str(basicStats['uniqueSubreddits']) + ' unique server(s). ' - statComment += 'A total of ' + str(basicStats['uniqueNames']) - statComment += ' unique anime/manga have been requested, with a mean value of ' + str(round(basicStats['meanValuePerRequest'],3)) - statComment += ' requests/show and a standard deviation of ' + str(round(basicStats['standardDeviation'], 3)) + '.' - - statComment += '\n\n' - - statComment += 'The most frequently requested anime/manga overall are:\n\n' - - for i, request in enumerate(basicStats['topRequests']): - statComment += str(i + 1) + '. **' + str(request[0]) + '** (' + str(request[1]) + ' - ' + str(request[2]) + ' requests)\n' - - - statComment += '\n' - receipt += ' - Basic' - - print(receipt.encode('utf8')) - localEmbed = buildEmbedObject('Stats', '', statComment, '', False, '') - return localEmbed - except: - traceback.print_exc() - return None - -def buildHelpEmbed(): - try: - embedTitle = "Help" - helpComment = "You can call the bot by using specific tags on one of the active servers. Anime can be called using {curly braces}, manga can be called using and light novels can be called using reverse square brace ]light novels\[ (e.g. {Nisekoi} or or ]Utsuro no Hako to Zero no Maria\[). {Single} ]will\[ give you a normal set of information while {{double}} <> ]]will\[\[ give you expanded information. Examples of these requests can be found [here](https://github.com/dashwav/Discordoragi/wiki/Example-Output)" - localEmbed = buildEmbedObject(embedTitle, '', helpComment, '', False, '') - return localEmbed - except: - traceback.print_exc() - return None - -def buildEmbedObject(embedTitle, embedLinks, embedContent, embedThumbnail, isExpanded, descComment): - - localFooterTitle='\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_' - localFooter = "{anime}, , \]LN\[ | [FAQ](https://github.com/dashwav/Discordoragi/wiki) | [/r/](http://www.reddit.com/r/Roboragi/) | [Discord](https://discord.gg/SNv9yKs) | [Source](https://github.com/dashwav/Discordoragi) | [Synonyms](https://www.reddit.com/r/Roboragi/wiki/synonyms)" - try: - embed = Embed(title=embedTitle, description=embedLinks, type='rich') - - embed.set_thumbnail(url = embedThumbnail) - - embed.add_field(name='__Info__', value=embedContent) - - if isExpanded: - if len(descComment.rstrip()) > 1023: - descCommentCut = descComment.rstrip()[:1020] + '...' - embed.add_field(name ='__Description__', value = descCommentCut) - else: - embed.add_field(name = '__Description__', value = descComment) - - embed.add_field(name=localFooterTitle, value=localFooter) - return embed - except Exception as e: - print(e) - -def sanitise_url_for_markdown(url): - return url.replace('(', '\(').replace(')', '\)') \ No newline at end of file diff --git a/roboragi_old/Config.py.example b/roboragi_old/Config.py.example deleted file mode 100644 index 3dc67be..0000000 --- a/roboragi_old/Config.py.example +++ /dev/null @@ -1,18 +0,0 @@ -#Bot Info - Register your bot here: https://discordapp.com/developers/applications/me -clientid='id' -username='username' -token='token' - -#Database Info -dbname='discordoragi' -dbuser='discordoragi' -dbpassword='password' -dbhost='localhost' - -#Mal Info -maluseragent='' # A basic description of your program -malauth='' # Follow the instructions here: http://en.wikipedia.org/wiki/Basic_access_authentication - -#Anilist Info - Create an account on AniList the go to the developer tab on your profile page -aniclient='' -anisecret='' \ No newline at end of file diff --git a/roboragi_old/DatabaseHandler.py b/roboragi_old/DatabaseHandler.py deleted file mode 100644 index 76ff05d..0000000 --- a/roboragi_old/DatabaseHandler.py +++ /dev/null @@ -1,605 +0,0 @@ -''' -DatabaseHandler.py -Handles all connections to the database. The database runs on PostgreSQL and is connected to via psycopg2. -''' - -import psycopg2 -from psycopg2 import sql -from psycopg2.extras import Json, DictCursor - -import datetime -from math import sqrt -import traceback -import discord - -DBNAME = '' -DBUSER = '' -DBPASSWORD = '' -DBHOST = '' - -try: - import Config - DBNAME = Config.dbname - DBUSER = Config.dbuser - DBPASSWORD = Config.dbpassword - DBHOST = Config.dbhost -except ImportError: - pass - -conn = psycopg2.connect("dbname='" + DBNAME + "' user='" + DBUSER + "' host='" + DBHOST + "' password='" + DBPASSWORD + "'") -cur = conn.cursor() - -#Sets up the database and creates the databases if they haven't already been made. -def setup(): - try: - conn = psycopg2.connect("dbname='" + DBNAME + "' user='" + DBUSER + "' host='" + DBHOST + "' password='" + DBPASSWORD + "'") - except: - print("Unable to connect to the database") - - cur = conn.cursor() - - #Create requests table - try: - cur.execute('CREATE TABLE requests ( id SERIAL PRIMARY KEY, name varchar(320), type varchar(16), requester varchar(50), server varchar(50), requesttimestamp timestamp DEFAULT current_timestamp)') - conn.commit() - except Exception as e: - #traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - - #Create messages table - try: - cur.execute('CREATE TABLE messages ( messageid varchar(32) PRIMARY KEY, requester varchar(50), server varchar(50), hadRequest boolean)') - conn.commit() - except Exception as e: - #traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - - #Create malAnime table - try: - cur.execute('CREATE TABLE malanime ( id varchar(16) PRIMARY KEY, name varchar(320) , synonyms varchar(320)[], accesstimestamp timestamp DEFAULT current_timestamp, dict JSONB)') - conn.commit() - except Exception as e: - #traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - - #Create malmanga table - try: - cur.execute('CREATE TABLE malmanga ( id varchar(16) PRIMARY KEY, name varchar(320) ,medium varchar(16), synonyms varchar(320)[], accesstimestamp timestamp DEFAULT current_timestamp, dict JSONB)') - conn.commit() - except Exception as e: - #traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - - #Create anilistanime table - try: - cur.execute('CREATE TABLE anilistanime ( id varchar(16) PRIMARY KEY, name varchar(320) , synonyms varchar(320)[], accesstimestamp timestamp DEFAULT current_timestamp, dict JSONB)') - conn.commit() - except Exception as e: - #traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - - #Create anilistmanga table - try: - cur.execute('CREATE TABLE anilistmanga ( id varchar(16) PRIMARY KEY, name varchar(320) ,medium varchar(16), synonyms varchar(320)[], accesstimestamp timestamp DEFAULT current_timestamp, dict JSONB)') - conn.commit() - except Exception as e: - #traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - - try: - cur.execute('CREATE TABLE serverconfig (serverid varchar(50) PRIMARY KEY, allowexpanded varchar(16), allowstats varchar(16))') - conn.commit() - except Exception as e: - #traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -setup() - -#--------------------------------------# -# Server config - -def addServerToDatabase(serverId): - try: - - cur = conn.cursor(cursor_factory = DictCursor) - cur.execute('SELECT * FROM serverconfig WHERE serverid = (%s)', [str(serverId)]) - row = cur.fetchone() - if row is None: - cur.execute('INSERT INTO serverconfig (serverid, allowexpanded, allowstats) VALUES (%s, %s, %s)', [serverId, 'true', 'true']) - except: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -def toggleAllowExpanded(serverId): - try: - cur = conn.cursor(cursor_factory = DictCursor) - cur.execute('SELECT * FROM serverconfig WHERE serverid = (%s)', [str(serverId)]) - row = cur.fetchone() - if row is not None: - if row['allowexpanded'].lower() == 'true': - toggledSetting = 'false' - else: - toggledSetting = 'true' - cur.execute('UPDATE serverconfig SET allowexpanded= %s WHERE serverid = %s', [toggledSetting, serverId]) - return toggledSetting - except: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -def checkServerConfig(setting, serverId): - try: - cur = conn.cursor(cursor_factory = DictCursor) - cur.execute('SELECT * FROM serverconfig WHERE serverid = (%s)', [str(serverId)]) - row = cur.fetchone() - if row is not None: - if row[setting] == 'true': - return True - else: - return False - except: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -#--------------------------------------# -# Caching -def addMalEntry(table, anime): - try: - cur = conn.cursor(cursor_factory=DictCursor) - animeName = anime['title'] - animeID = anime['id'] - synonyms = anime['synonyms'] - animeSyn = [] - animeSyn.append(animeName.lower()) - if anime['synonyms']: - for synonym in anime['synonyms']: - animeSyn.append(synonym.lower().strip()) - if anime['english']: - animeSyn.append(anime['english'].lower()) - - cur.execute(sql.SQL("SELECT * FROM {} WHERE id = (%s)").format(sql.Identifier(table)), [str(animeID)]) - row = cur.fetchone() - - if row is not None: - timeDiff = datetime.datetime.now() - row['accesstimestamp'] - if timeDiff.days >= 1: - cur.execute(sql.SQL("UPDATE {} SET synonyms = %s, dict = %s, accesstimestamp = current_timestamp WHERE id = %s").format(sql.Identifier(table)), [animeSyn, Json(anime), str(animeID)]) - conn.commit() - print("updated info") - return - else: - return - - if 'novel' in anime['type'].lower() or 'manga' in anime['type'].lower(): - if 'novel' in anime['type'].lower(): - print("adding ln to mal") - novelOrManga = 'light novel' - else: - novelOrManga = 'manga' - - cur.execute(sql.SQL("INSERT into {} (id, name, medium, synonyms, dict) values (%s, %s, %s, %s, %s)").format(sql.Identifier(table)), [ str(animeID), animeName.lower(), novelOrManga, animeSyn, Json(anime)]) - conn.commit() - return - - cur.execute(sql.SQL("INSERT into {} (id, name, synonyms, dict) values (%s, %s, %s, %s)").format(sql.Identifier(table)), [ str(animeID), animeName.lower(), animeSyn, Json(anime)]) - conn.commit() - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -def addAniEntry(table, anime): - try: - cur = conn.cursor(cursor_factory=DictCursor) - if anime['title_english']: - animeName = anime['title_english'] - elif anime['title_romaji']: - animeName = anime['title_romaji'] - animeID = anime['id'] - synonyms = anime['synonyms'] - novelOrManga = 'manga' - animeSyn = [] - animeSyn.append(animeName.lower()) - if anime['synonyms']: - for synonym in anime['synonyms']: - animeSyn.append(synonym.lower().strip()) - if anime['title_english']: - animeSyn.append(anime['title_english'].lower()) - elif anime['title_romaji']: - animeSyn.append(anime['title_romaji'].lower()) - - cur.execute(sql.SQL("SELECT * FROM {} WHERE id = (%s)").format(sql.Identifier(table)), [str(animeID)]) - row = cur.fetchone() - - if row is not None: - timeDiff = datetime.datetime.now() - row['accesstimestamp'] - if timeDiff.days >= 1: - cur.execute(sql.SQL("UPDATE {} SET synonyms = %s, dict = %s, accesstimestamp = current_timestamp WHERE id = %s").format(sql.Identifier(table)), [animeSyn, Json(anime), str(animeID)]) - conn.commit() - print("updated info") - return - else: - return - if anime['series_type'] == 'manga': - if anime['type'] == 'Novel': - print("light novel being added") - novelOrManga = 'light novel' - cur.execute(sql.SQL("INSERT into {} (id, name, medium, synonyms, dict) values (%s, %s, %s, %s, %s)").format(sql.Identifier(table)), [ str(animeID), animeName.lower(), novelOrManga, animeSyn, Json(anime)]) - conn.commit() - return - - cur.execute(sql.SQL("INSERT into {} (id, name, synonyms, dict) values (%s, %s, %s, %s)").format(sql.Identifier(table)), [ str(animeID), animeName.lower(), animeSyn, Json(anime)]) - conn.commit() - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -def checkForMalEntry(table, name, animeId = None, isLN = None): - try: - cur = conn.cursor(cursor_factory=DictCursor) - nameInList = '{'+name.lower().strip()+'}' - if animeId is not None: - cur.execute(sql.SQL("SELECT * FROM {} WHERE id = (%s)").format(sql.Identifier(table)), [str(animeId)]) - else: - if table == 'malmanga' or table == 'anilistmanga': - if isLN: - cur.execute(sql.SQL("SELECT * FROM {} WHERE medium = %s AND synonyms @> %s").format(sql.Identifier(table)), ['light novel', nameInList]) - else: - cur.execute(sql.SQL("SELECT * FROM {} WHERE medium = %s AND synonyms @> %s").format(sql.Identifier(table)), ['manga', nameInList]) - else: - cur.execute(sql.SQL("SELECT * FROM {} WHERE synonyms @> %s").format(sql.Identifier(table)), [nameInList]) - row = cur.fetchone() - cachedReply = {} - - if row is not None: - #print("found cached entry") - timeDiff = datetime.datetime.now() - row['accesstimestamp'] - if timeDiff.days >= 1: - cachedReply['update'] = True - cachedReply['id'] = row['id'] - else: - cachedReply['update'] = False - cachedReply['content'] = row['dict'] - - return cachedReply - #print("didn't find cached entry in mal") - return None - - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -def PopulateCache(table, content): - setup() - novelOrManga = 'manga' - try: - cur = conn.cursor(cursor_factory=DictCursor) - if table == 'malanime' or table == 'malmanga': - animeName = content['title'] - animeID = content['id'] - synonyms = content['synonyms'] - if 'novel' in content['type']: - print("adding ln to mal") - novelOrManga = 'light novel' - animeSyn = [] - animeSyn.append(animeName.lower()) - if content['synonyms']: - for synonym in content['synonyms']: - animeSyn.append(synonym.lower().strip()) - if content['english']: - animeSyn.append(content['english'].lower()) - else: - if content['title_english']: - animeName = content['title_english'] - elif content['title_romaji']: - animeName = content['title_romaji'] - if content['type'] == 'Novel': - print("light novel being added to ani") - novelOrManga = 'light novel' - animeID = content['id'] - synonyms = content['synonyms'] - animeSyn = [] - animeSyn.append(animeName.lower()) - if content['synonyms']: - for synonym in content['synonyms']: - animeSyn.append(synonym.lower().strip()) - - - if content['title_english']: - animeSyn.append(content['title_english'].lower()) - elif content['title_romaji']: - animeSyn.append(content['title_romaji'].lower()) - - cur.execute(sql.SQL("SELECT * FROM {} WHERE id = (%s)").format(sql.Identifier(table)), [str(animeID)]) - row = cur.fetchone() - - if row is not None: - return - else: - expired_date = "1999-01-08 04:05:06" - if table =='malmanga' or table == 'anilistmanga': - cur.execute(sql.SQL("INSERT into {} (id, name, medium, synonyms, accesstimestamp, dict) values (%s, %s, %s, %s, %s, %s)").format(sql.Identifier(table)), [ str(animeID), animeName.lower(), novelOrManga, animeSyn, expired_date, Json(content)]) - conn.commit() - return - cur.execute(sql.SQL("INSERT into {} (id, name, synonyms, accesstimestamp, dict) values (%s, %s, %s, %s, %s)").format(sql.Identifier(table)), [ str(animeID), animeName.lower(), animeSyn, expired_date, Json(content)]) - print("Added {} to the {}:\n".format(animeName, table)) - conn.commit() - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -#--------------------------------------# - -# Adds a message to the "already seen" database. Also handles submissions, which have a similar ID structure. -def addMessage(messageid, requester, serverid, hadRequest): - try: - server = serverid.lower() - - cur.execute('INSERT INTO messages (messageid, requester, server, hadRequest) VALUES (%s, %s, %s, %s)', (messageid, requester, server, hadRequest)) - conn.commit() - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -#Returns true if the message/submission has already been checked. -def messageExists(messageid): - try: - cur.execute('SELECT * FROM messages WHERE messageid = %s', (messageid,)) - if (cur.fetchone()) is None: - conn.commit() - return False - else: - conn.commit() - return True - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - return True - -#Adds a request to the request-tracking database. rType is either "Anime" or "Manga". -def addRequest(name, rType, requester, serverid): - try: - server = serverid.lower() - - if ('nihilate' not in server): - cur.execute('INSERT INTO requests (name, type, requester, server) VALUES (%s, %s, %s, %s)', (name, rType, requester, server)) - conn.commit() - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - -#Returns an object which contains data about the overall database stats (i.e. ALL servers). -def getBasicStats(serverID, top_media_number=5, top_username_number=5): - try: - basicStatDict = {} - - cur.execute("SELECT COUNT(1) FROM messages") - totalComments = int(cur.fetchone()[0]) - basicStatDict['totalComments'] = totalComments - - cur.execute("SELECT COUNT(1) FROM requests;") - total = int(cur.fetchone()[0]) - basicStatDict['total'] = total - - cur.execute("SELECT COUNT(1) FROM (SELECT DISTINCT name FROM requests) as temp;") - dNames = int(cur.fetchone()[0]) - basicStatDict['uniqueNames'] = dNames - - cur.execute("SELECT COUNT(1) FROM (SELECT DISTINCT server FROM requests) as temp;") - dSubreddits = int(cur.fetchone()[0]) - basicStatDict['uniqueSubreddits'] = dSubreddits - - meanValue = float(total)/dNames - basicStatDict['meanValuePerRequest'] = meanValue - - variance = 0 - cur.execute("SELECT name, count(name) FROM requests GROUP by name") - for entry in cur.fetchall(): - variance += (entry[1] - meanValue) * (entry[1] - meanValue) - - variance = variance / dNames - stdDev = sqrt(variance) - basicStatDict['standardDeviation'] = stdDev - - cur.execute("SELECT name, type, COUNT(name) FROM requests GROUP BY name, type ORDER BY COUNT(name) DESC, name ASC LIMIT %s", (top_media_number,)) - topRequests = cur.fetchall() - basicStatDict['topRequests'] = [] - for request in topRequests: - basicStatDict['topRequests'].append(request) - - cur.execute("SELECT requester, COUNT(requester), server, COUNT(server) FROM requests WHERE server = %s GROUP BY requester, server ORDER BY COUNT(requester) DESC, requester ASC, COUNT(server) DESC, server ASC LIMIT %s", (serverID, top_username_number,)) - topRequesters = cur.fetchall() - basicStatDict['topRequesters'] = [] - for requester in topRequesters: - basicStatDict['topRequesters'].append(requester) - - conn.commit() - return basicStatDict - - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - return None - -#Returns an object which contains request-specifc data. Basically just used for the expanded comments. -def getRequestStats(requestName, type): - try: - basicRequestDict = {} - - requestType = type - - cur.execute("SELECT COUNT(*) FROM requests") - total = int(cur.fetchone()[0]) - - cur.execute("SELECT COUNT(*) FROM requests WHERE name = %s AND type = %s", (requestName, requestType)) - requestTotal = int(cur.fetchone()[0]) - basicRequestDict['total'] = requestTotal - - if requestTotal == 0: - return None - - cur.execute("SELECT COUNT(DISTINCT server) FROM requests WHERE name = %s AND type = %s", (requestName, requestType)) - dSubreddits = int(cur.fetchone()[0]) - basicRequestDict['uniqueSubreddits'] = dSubreddits - - totalAsPercentage = (float(requestTotal)/total) * 100 - basicRequestDict['totalAsPercentage'] = totalAsPercentage - - conn.commit() - return basicRequestDict - - except: - cur.execute('ROLLBACK') - conn.commit() - return None - -#Returns an object which contains data about the overall database stats (i.e. ALL servers). -def getUserStats(username, top_media_number=5): - try: - basicUserStatDict = {} - username = str(username).lower() - - """ - cur.execute("SELECT COUNT(1) FROM messages where LOWER(requester) = %s", (username,)) - totalUserComments = int(cur.fetchone()[0]) - basicUserStatDict['totalUserComments'] = totalUserComments - - - cur.execute("SELECT COUNT(1) FROM messages") - totalNumComments = int(cur.fetchone()[0]) - totalCommentsAsPercentage = (float(totalUserComments)/totalNumComments) * 100 - basicUserStatDict['totalUserCommentsAsPercentage'] = totalCommentsAsPercentage - """ - - cur.execute("SELECT COUNT(*) FROM requests where LOWER(requester) = %s", (username,)) - totalUserRequests = int(cur.fetchone()[0]) - basicUserStatDict['totalUserRequests'] = totalUserRequests - - cur.execute("SELECT COUNT(1) FROM requests") - totalNumRequests = int(cur.fetchone()[0]) - totalRequestsAsPercentage = (float(totalUserRequests)/totalNumRequests) * 100 - basicUserStatDict['totalUserRequestsAsPercentage'] = totalRequestsAsPercentage - - cur.execute('''SELECT row FROM - (SELECT requester, count(1), ROW_NUMBER() over (order by count(1) desc) as row - from requests - group by requester) as overallrequestrank - where lower(requester) = %s''', (username,)) - overallRequestRank = int(cur.fetchone()[0]) - basicUserStatDict['overallRequestRank'] = overallRequestRank - - cur.execute("SELECT COUNT(DISTINCT (name, type)) FROM requests WHERE LOWER(requester) = %s", (username,)) - uniqueRequests = int(cur.fetchone()[0]) - basicUserStatDict['uniqueRequests'] = uniqueRequests - - - cur.execute('''select r.server, count(r.server), total.totalcount from requests r - inner join (select server, count(server) as totalcount from requests - group by server) total on total.server = r.server - where LOWER(requester) = %s - group by r.server, total.totalcount - order by count(r.server) desc - limit 1 - ''', (username,)) - favouriteSubredditStats = cur.fetchone() - favouriteSubreddit = str(favouriteSubredditStats[0]) - favouriteSubredditCount = int(favouriteSubredditStats[1]) - favouriteSubredditOverallCount = int(favouriteSubredditStats[2]) - basicUserStatDict['favouriteSubreddit'] = favouriteSubreddit - basicUserStatDict['favouriteSubredditCount'] = favouriteSubredditCount - basicUserStatDict['favouriteSubredditCountAsPercentage'] = (float(favouriteSubredditCount)/favouriteSubredditOverallCount) * 100 - - cur.execute('''SELECT name, type, COUNT(name) FROM requests where LOWER(requester) = %s - GROUP BY name, type ORDER BY COUNT(name) DESC, name ASC LIMIT %s''', (username, top_media_number)) - topRequests = cur.fetchall() - basicUserStatDict['topRequests'] = [] - for request in topRequests: - basicUserStatDict['topRequests'].append(request) - - conn.commit() - return basicUserStatDict - - except Exception as e: - cur.execute('ROLLBACK') - conn.commit() - return None - -#Similar to getBasicStats - returns an object which contains data about a specific server. -def getSubredditStats(server, top_media_number=5, top_username_number=5): - try: - basicSubredditDict = {} - print(server.name+"\n") - print(server.id+"\n") - serverID = server.id - - """ - cur.execute("SELECT COUNT(*) FROM messages WHERE server = %s", (serverID,)) - totalComments = int(cur.fetchone()[0]) - basicSubredditDict['totalComments'] = totalComments - """ - - cur.execute("SELECT COUNT(*) FROM requests;") - total = int(cur.fetchone()[0]) - - cur.execute("SELECT COUNT(*) FROM requests WHERE server = %s", (serverID,)) - sTotal = int(cur.fetchone()[0]) - basicSubredditDict['total'] = sTotal - - if sTotal == 0: - return None - - cur.execute("SELECT COUNT(DISTINCT (name, type)) FROM requests WHERE server = %s", (serverID,)) - dNames = int(cur.fetchone()[0]) - basicSubredditDict['uniqueNames'] = dNames - - totalAsPercentage = (float(sTotal)/total) * 100 - basicSubredditDict['totalAsPercentage'] = totalAsPercentage - - meanValue = float(sTotal)/dNames - basicSubredditDict['meanValuePerRequest'] = meanValue - - variance = 0 - cur.execute("SELECT name, type, count(name) FROM requests WHERE server = %s GROUP by name, type", (serverID,)) - for entry in cur.fetchall(): - variance += (entry[2] - meanValue) * (entry[2] - meanValue) - - variance = variance / dNames - stdDev = sqrt(variance) - basicSubredditDict['standardDeviation'] = stdDev - - cur.execute("SELECT name, type, COUNT(name) FROM requests WHERE server = %s GROUP BY name, type ORDER BY COUNT(name) DESC, name ASC LIMIT %s", (serverID, top_media_number)) - topRequests = cur.fetchall() - basicSubredditDict['topRequests'] = [] - for request in topRequests: - basicSubredditDict['topRequests'].append(request) - - cur.execute("SELECT requester, COUNT(requester) FROM requests WHERE server = %s GROUP BY requester ORDER BY COUNT(requester) DESC, requester ASC LIMIT %s", (serverID, top_username_number)) - topRequesters = cur.fetchall() - basicSubredditDict['topRequesters'] = [] - for requester in topRequesters: - basicSubredditDict['topRequesters'].append(requester) - - conn.commit() - - return basicSubredditDict - except Exception as e: - traceback.print_exc() - cur.execute('ROLLBACK') - conn.commit() - return None diff --git a/roboragi_old/Discord.py b/roboragi_old/Discord.py deleted file mode 100644 index 3d0a1de..0000000 --- a/roboragi_old/Discord.py +++ /dev/null @@ -1,28 +0,0 @@ -''' -Discord.py -Used for communication with Discord -''' - -import discord -import asyncio - -try: - import Config - print('Getting Config Info') - TOKEN = Config.token -except ImportError: - pass - -client = discord.Client() - -def run(): - client.run(TOKEN) - -def getMemberFromID(userID, server): - return discord.utils.get(server.members, id=userID) - -def getServerFromID(serverID): - return discord.utils.get(Discord.client.servers, id=serverID) - -def getServerFromName(serverName): - return discord.utils.get(Discord.client.servers, name=serverName) diff --git a/roboragi_old/DiscordoragiSearch.py b/roboragi_old/DiscordoragiSearch.py deleted file mode 100644 index f871862..0000000 --- a/roboragi_old/DiscordoragiSearch.py +++ /dev/null @@ -1,482 +0,0 @@ -''' -DiscordoragiSearch .py -Returns a built comment created from multiple databases when given a search term. -''' - -import MAL -import AnimePlanet as AniP -import AniDB -import Hummingbird -import Anilist -import MU -import NU -import LNDB - -import CommentBuilder -import DatabaseHandler - -import traceback -import time - -import sqlite3 -import json - -import asyncio -import pprint - -USERNAME = '' - -try: - import Config - USERNAME = Config.username -except ImportError: - pass - -sqlConn = sqlite3.connect('synonyms.db') -sqlCur = sqlConn.cursor() - -try: - sqlCur.execute('SELECT dbLinks FROM synonyms WHERE type = "Manga" and lower(name) = ?', ["despair simulator"]) -except sqlite3.Error: - traceback.print_exc() - -#Checks if the message is valid (i.e. not already seen, not a post by Roboragi and the parent commenter isn't Roboragi) -def isValidMessage(message): - try: - if (DatabaseHandler.messageExists(message.id)): - return False - - try: - if (message.author.name == USERNAME): - DatabaseHandler.addMessage(message.id, message.author.id, message.server.id, False) - return False - except: - pass - - return True - - except: - traceback.print_exc() - return False - -#Builds a manga reply from multiple sources -async def buildMangaReply(searchText, message, isExpanded, canEmbed, blockTracking=False): - try: - ani = None - mal = None - mu = None - ap = None - - try: - sqlCur.execute('SELECT dbLinks FROM synonyms WHERE type = "Manga" and lower(name) = ?', [searchText.lower()]) - except sqlite3.Error as e: - print(e) - - alternateLinks = sqlCur.fetchone() - - if (alternateLinks): - synonym = json.loads(alternateLinks[0]) - - if 'mal' in synonym: - if (synonym['mal']): - mal = await MAL.getMangaDetails(synonym['mal'][0], synonym['mal'][1]) - - if 'ani' in synonym: - if (synonym['ani']): - ani = await Anilist.getMangaDetailsById(synonym['ani']) - - if 'mu' in synonym: - if (synonym['mu']): - mu = MU.getMangaURLById(synonym['mu']) - - if 'ap' in synonym: - if (synonym['ap']): - ap = AniP.getMangaURLById(synonym['ap']) - - else: - #Basic breakdown: - #If Anilist finds something, use it to find the MAL version. - #If hits either MAL or Ani, use it to find the MU version. - #If it hits either, add it to the request-tracking DB. - ani = await Anilist.getMangaDetails(searchText) - - if ani: - try: - mal = await MAL.getMangaDetails(ani['title_romaji']) - except Exception as e: - print(e) - pass - - if not mal: - try: - mal = await MAL.getMangaDetails(ani['title_english']) - except: - pass - - if not mal: - mal = await MAL.getMangaDetails(searchText) - - else: - mal = await MAL.getMangaDetails(searchText) - - if mal: - ani = await Anilist.getMangaDetails(mal['title']) - - #----- Finally... -----# - if ani or mal: - try: - titleToAdd = '' - if mal: - titleToAdd = mal['title'] - else: - try: - titleToAdd = ani['title_english'] - except: - titleToAdd = ani['title_romaji'] - - - if not alternateLinks: - #MU stuff - if mal: - mu = await MU.getMangaURL(mal['title']) - else: - mu = await MU.getMangaURL(ani['title_romaji']) - - #Do the anime-planet stuff - if mal and not ap: - if mal['title'] and not ap: - ap = await AniP.getMangaURL(mal['title']) - if mal['english'] and not ap: - ap = await AniP.getMangaURL(mal['english']) - if mal['synonyms'] and not ap: - for synonym in mal['synonyms']: - if ap: - break - ap = await AniP.getMangaURL(synonym) - - if ani and not ap: - if ani['title_english'] and not ap: - ap = await AniP.getMangaURL(ani['title_english']) - if ani['title_romaji'] and not ap: - ap = await AniP.getMangaURL(ani['title_romaji']) - if ani['synonyms'] and not ap: - for synonym in ani['synonyms']: - if ap: - break - ap = await AniP.getMangaURL(synonym) - if not blockTracking: - DatabaseHandler.addRequest(titleToAdd, 'Manga', message.author.id, message.server.id) - except: - traceback.print_exc() - pass - if mal: - try: - DatabaseHandler.addMalEntry('malmanga', mal) - except: - traceback.print_exc() - pass - if ani: - try: - DatabaseHandler.addAniEntry('anilistmanga', ani) - except: - traceback.print_exc() - pass - if not canEmbed: - return CommentBuilder.buildMangaComment(isExpanded, mal, ani, mu, ap) - else: - return CommentBuilder.buildMangaEmbed(isExpanded, mal, ani, mu, ap) - except Exception as e: - traceback.print_exc() - return None - -#Builds a manga search for a specific series by a specific author -async def buildMangaReplyWithAuthor(searchText, authorName, message, isExpanded, canEmbed, blockTracking=False): - try: - ani = await Anilist.getMangaWithAuthor(searchText, authorName) - mal = None - mu = None - ap = None - - if ani: - try: - mal = await MAL.getMangaCloseToDescription(searchText, ani['description']) - ap = await AniP.getMangaURL(ani['title_english'], authorName) - except Exception as e: - print(e) - else: - ap = await AniP.getMangaURL(searchText, authorName) - - mu = await MU.getMangaWithAuthor(searchText, authorName) - - if ani: - try: - titleToAdd = '' - if mal is not None: - titleToAdd = mal['title'] - else: - titleToAdd = ani['title_english'] - - if not blockTracking: - DatabaseHandler.addRequest(titleToAdd, 'Manga', message.author.id, message.server.id) - except: - traceback.print_exc() - pass - - if not canEmbed: - return CommentBuilder.buildMangaComment(isExpanded, mal, ani, mu, ap) - else: - return CommentBuilder.buildMangaEmbed(isExpanded, mal, ani, mu, ap) - - except Exception as e: - traceback.print_exc() - return None - -#Builds an anime reply from multiple sources -async def buildAnimeReply(searchText, message, isExpanded, canEmbed, blockTracking=False): - try: - mal = {'search_function': MAL.getAnimeDetails, - 'synonym_function': MAL.getSynonyms, - 'checked_synonyms': [], - 'result': None} - ani = {'search_function': Anilist.getAnimeDetails, - 'synonym_function': Anilist.getSynonyms, - 'checked_synonyms': [], - 'result': None} - ap = {'search_function': AniP.getAnimeURL, - 'result': None} - adb = {'search_function': AniDB.getAnimeURL, - 'result': None} - - try: - sqlCur.execute('SELECT dbLinks FROM synonyms WHERE type = "Anime" and lower(name) = ?', [searchText.lower()]) - except sqlite3.Error as e: - print(e) - - alternateLinks = sqlCur.fetchone() - - if (alternateLinks): - synonym = json.loads(alternateLinks[0]) - - if synonym: - malsyn = None - if 'mal' in synonym and synonym['mal']: - malsyn = synonym['mal'] - anisyn = None - if 'ani' in synonym and synonym['ani']: - anisyn = synonym['ani'] - - apsyn = None - if 'ap' in synonym and synonym['ap']: - apsyn = synonym['ap'] - - adbsyn = None - if 'adb' in synonym and synonym['adb']: - adbsyn = synonym['adb'] - - mal['result'] = await MAL.getAnimeDetails(malsyn[0],malsyn[1]) if malsyn else None - ani['result'] = await Anilist.getAnimeDetailsById(anisyn) if anisyn else None - ap['result'] = AniP.getAnimeURLById(apsyn) if apsyn else None - adb['result'] = AniDB.getAnimeURLById(adbsyn) if adbsyn else None - print(ani['result']) - - else: - data_sources = [ani, mal] - aux_sources = [ap, adb] - #aux_sources = [ap] - - synonyms = set([searchText]) - - for x in range(len(data_sources)): - for source in data_sources: - if source['result']: - break - else: - for synonym in synonyms: - if synonym in source['checked_synonyms']: - continue - - source['result'] = await source['search_function'](synonym) - source['checked_synonyms'].append(synonym) - - if source['result']: - break - - if source['result']: - synonyms.update([synonym.lower() for synonym in source['synonym_function'](source['result'])]) - - for source in aux_sources: - for synonym in synonyms: - source['result'] = await source['search_function'](synonym) - - if source['result']: - break - - if ani['result'] or mal['result']: - try: - titleToAdd = '' - if mal['result']: - if 'title' in mal['result']: - titleToAdd = mal['result']['title'] - '''if hb['result']: - if 'title' in hb['result']: - titleToAdd = hb['result']['title']''' - if ani['result']: - if 'title_romaji' in ani['result']: - titleToAdd = ani['result']['title_romaji'] - - if not blockTracking: - DatabaseHandler.addRequest(titleToAdd, 'Anime', message.author.id, message.server.id) - except: - traceback.print_exc() - pass - if mal['result']: - print('trying to add an anime to cache') - try: - DatabaseHandler.addMalEntry('malanime', mal['result']) - except: - traceback.print_exc() - pass - if ani: - try: - DatabaseHandler.addAniEntry('anilistanime', ani['result']) - except: - traceback.print_exc() - pass - if not canEmbed: - return CommentBuilder.buildAnimeComment(isExpanded, mal['result'], ani['result'], ap['result'], adb['result']) - else: - return CommentBuilder.buildAnimeEmbed(isExpanded, mal['result'], ani['result'], ap['result'], adb['result']) - - except Exception as e: - traceback.print_exc() - return None - -#Builds an LN reply from multiple sources -async def buildLightNovelReply(searchText, isExpanded, message, canEmbed, blockTracking=False): - try: - mal = {'search_function': MAL.getLightNovelDetails, - 'synonym_function': MAL.getSynonyms, - 'checked_synonyms': [], - 'result': None} - ani = {'search_function': Anilist.getLightNovelDetails, - 'synonym_function': Anilist.getSynonyms, - 'checked_synonyms': [], - 'result': None} - nu = {'search_function': NU.getLightNovelURL, - 'result': None} - lndb = {'search_function': LNDB.getLightNovelURL, - 'result': None} - - try: - sqlCur.execute('SELECT dbLinks FROM synonyms WHERE type = "LN" and lower(name) = ?', [searchText.lower()]) - except sqlite3.Error as e: - print(e) - - alternateLinks = sqlCur.fetchone() - - if (alternateLinks): - synonym = json.loads(alternateLinks[0]) - - if synonym: - malsyn = None - if 'mal' in synonym and synonym['mal']: - malsyn = synonym['mal'] - - anisyn = None - if 'ani' in synonym and synonym['ani']: - anisyn = synonym['ani'] - - nusyn = None - if 'nu' in synonym and synonym['nu']: - nusyn = synonym['nu'] - - lndbsyn = None - if 'lndb' in synonym and synonym['lndb']: - lndbsyn = synonym['lndb'] - - mal['result'] = await MAL.getLightNovelDetails(malsyn[0],malsyn[1]) if malsyn else None - ani['result'] = await Anilist.getMangaDetailsById(anisyn) if anisyn else None - nu['result'] = NU.getLightNovelById(nusyn) if nusyn else None - lndb['result'] =LNDB.getLightNovelById(lndbsyn) if lndbsyn else None - - else: - data_sources = [ani, mal] - aux_sources = [nu, lndb] - - synonyms = set([searchText]) - - for x in range(len(data_sources)): - for source in data_sources: - if source['result']: - break - else: - for synonym in synonyms: - if synonym in source['checked_synonyms']: - continue - - source['result'] = await source['search_function'](synonym) - source['checked_synonyms'].append(synonym) - - if source['result']: - break - - if source['result']: - synonyms.update([synonym.lower() for synonym in source['synonym_function'](source['result'])]) - - for source in aux_sources: - for synonym in synonyms: - source['result'] =await source['search_function'](synonym) - - if source['result']: - break - - if ani['result'] or mal['result']: - try: - titleToAdd = '' - if mal['result']: - titleToAdd = mal['result']['title'] - if ani['result']: - try: - titleToAdd = ani['result']['title_romaji'] - except: - titleToAdd = ani['result']['title_english'] - - if (str(message.server).lower is not 'nihilate') and (str(message.server).lower is not 'roboragi') and not blockTracking: - DatabaseHandler.addRequest(titleToAdd, 'LN', message.author.id, message.server.id) - except: - traceback.print_exc() - pass - if mal['result']: - try: - DatabaseHandler.addMalEntry('malmanga', mal['result']) - except: - traceback.print_exc() - pass - if ani['result']: - try: - DatabaseHandler.addAniEntry('anilistmanga', ani['result']) - except: - traceback.print_exc() - pass - if not canEmbed: - return CommentBuilder.buildLightNovelComment(isExpanded, mal['result'], ani['result'], nu['result'], lndb['result']) - else: - return CommentBuilder.buildLightNovelEmbed(isExpanded, mal['result'], ani['result'], nu['result'], lndb['result']) - except Exception as e: - traceback.print_exc() - return None - -#Checks if the bot is the parent of this comment. -def isBotAParent(comment, reddit): - try: - parentComment = reddit.get_info(thing_id=comment.parent_id) - - if (parentComment.author.name == USERNAME): - return True - else: - return False - - except: - #traceback.print_exc() - return False - diff --git a/roboragi_old/Hummingbird.py b/roboragi_old/Hummingbird.py deleted file mode 100644 index bc83ea9..0000000 --- a/roboragi_old/Hummingbird.py +++ /dev/null @@ -1,69 +0,0 @@ -''' -Hummingbird.py -Handles all of the connections to Hummingbird. -''' -import aiohttp -import difflib -import requests -import traceback -import pprint - -session = aiohttp.ClientSession() - -def getSynonyms(request): - synonyms = [] - - synonyms.append(request['title']) if request['title'] else None - synonyms.append(request['alternate_title']) if request['alternate_title'] else None - - return synonyms - -#Returns the closest anime (as a Json-like object) it can find using the given searchtext -async def getAnimeDetails(searchText): - try: - request = await session.get('https://hummingbird.me/api/v1/search/anime?query=' + searchText.lower(), timeout=10) - closestAnime = getClosestAnime(searchText, request.json()) - - if not (closestAnime is None): - return closestAnime - else: - return None - - except Exception as e: - print(e) - return None - -#Returns the closest anime by id -async def getAnimeDetailsById(animeId): - try: - response = await session.get('http://hummingbird.me/api/v1/anime/' + str(animeId), timeout=10) - - return response.json() - except Exception as e: - return None - -#Sometimes the "right" anime isn't at the top of the list, so we get the titles -#of everything and do some fuzzy string searching against the search text -def getClosestAnime(searchText, animeList): - try: - animeNameList = [] - - for anime in animeList: - animeNameList.append(anime['title'].lower()) - - if anime['alternate_title'] is not None: - animeNameList.append(anime['alternate_title'].lower()) - - closestNameFromList = difflib.get_close_matches(searchText.lower(), animeNameList, 1, 0.95)[0] - - - for anime in animeList: - if anime['title'].lower() == closestNameFromList.lower(): - return anime - elif anime['alternate_title'] is not None: - if anime['alternate_title'].lower() == closestNameFromList.lower(): - return anime - - return None - except: - return None diff --git a/roboragi_old/LNDB.py b/roboragi_old/LNDB.py deleted file mode 100644 index 25d142a..0000000 --- a/roboragi_old/LNDB.py +++ /dev/null @@ -1,67 +0,0 @@ -''' -LNDB.py -Handles all LNDB information -''' - -from pyquery import PyQuery as pq -import requests -import aiohttp -import difflib -import traceback -import pprint -import collections - -session = aiohttp.ClientSession() - -async def getLightNovelURL(searchText): - try: - searchText = searchText.replace(' ', '+') - async with session.get('http://lndb.info/search?text=' + searchText, timeout=10) as resp: - html = await resp.text() - - lndb = pq(html) - - lnList = [] - - if 'light_novel' in html.url: - #we've immediately hit a result - return html.url - else: - #scan the search page for stuff - - lnList = [] - - for thing in lndb.find('#bodylightnovelscontentid table tr'): - title = pq(thing).find('a').text() - url = pq(thing).find('a').attr('href') - - if title: - data = { 'title': title, - 'url': url } - lnList.append(data) - - closest = findClosestLightNovel(searchText, lnList) - return closest['url'] - - except Exception as e: - return None - -def findClosestLightNovel(searchText, lnList): - try: - nameList = [] - - for ln in lnList: - nameList.append(ln['title'].lower()) - - closestNameFromList = difflib.get_close_matches(searchText.lower(), nameList, 1, 0.80) - - for ln in lnList: - if ln['title'].lower() == closestNameFromList[0].lower(): - return ln - - return None - except: - return None - -def getLightNovelById(lnId): - return 'http://lndb.info/light_novel/' + str(lnId) diff --git a/roboragi_old/MAL.py b/roboragi_old/MAL.py deleted file mode 100644 index 75ab87d..0000000 --- a/roboragi_old/MAL.py +++ /dev/null @@ -1,412 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -MAL.py -Handles all of the connections to MyAnimeList. -""" - -import xml.etree.cElementTree as ET -import DatabaseHandler -import aiohttp -import traceback -import pprint -import difflib -import urllib - -try: - import Config - print('Setting up MAL Connection') - MALUSERAGENT = Config.maluseragent - MALAUTH = Config.malauth -except ImportError: - pass - -try: - mal = aiohttp.ClientSession(headers = {'Authorization': MALAUTH, 'User-Agent': MALUSERAGENT}) -except Exception as e: - print(e) - - -#Sets up the connection to MAL. -def setup(): - mal = aiohttp.ClientSession(headers = {'Authorization': MALAUTH, 'User-Agent': MALUSERAGENT}) - -def getSynonyms(request): - synonyms = [] - - synonyms.append(request['title']) if request['title'] else None - synonyms.append(request['english']) if request['english'] else None - synonyms.extend(request['synonyms']) if request['synonyms'] else None - - return synonyms - -#Returns the closest anime (as a Json-like object) it can find using the given searchtext. MAL returns XML (bleh) so we have to convert it ourselves. -async def getAnimeDetails(searchText, animeId=None): - cachedAnime = DatabaseHandler.checkForMalEntry('malanime', searchText, animeId) - if cachedAnime is not None: - if cachedAnime['update']: - print("found cached anime, needs update in mal") - pass - else: - print("found cached anime, doesn't need update in mal") - return cachedAnime['content'] - - cleanSearchText = urllib.parse.quote(searchText) - try: - try: - async with mal.get('https://myanimelist.net/api/anime/search.xml?q=' + cleanSearchText.rstrip(), timeout=10) as resp: - if resp.status != 200: - print("Searching for {} failed with error code {}".format(searchText.rstrip(), resp.status)) - request = await resp.text() - except Exception as e: - print(e) - setup() - try: - async with mal.get('https://myanimelist.net/api/anime/search.xml?q=' + searchText.rstrip(), timeout=10) as resp: - request = await resp.text() - except aiohttp.exceptions.RequestException as e: # This is the correct syntax - print(e) - - #convertedRequest = convertShittyXML(request) - rawList = ET.fromstring(request) - - - animeList = [] - - for anime in rawList.findall('./entry'): - animeID = anime.find('id').text - title = anime.find('title').text - title_english = anime.find('english').text - - synonyms = None - if anime.find('synonyms').text is not None: - synonyms = anime.find('synonyms').text.split(";") - - episodes = anime.find('episodes').text - animeType = anime.find('type').text - status = anime.find('status').text - start_date = anime.find('start_date').text - end_date = anime.find('end_date').text - synopsis = anime.find('synopsis').text - image = anime.find('image').text - - data = {'id': animeID, - 'title': title, - 'english': title_english, - 'synonyms': synonyms, - 'episodes': episodes, - 'type': animeType, - 'status': status, - 'start_date': start_date, - 'end_date': end_date, - 'synopsis': synopsis, - 'image': image} - - animeList.append(data) - - if animeId: - closestAnime = getThingById(animeId, animeList) - elif cachedAnime and cachedAnime['update']: - closestAnime = getThingById(cachedAnime['id'], animeList) - else: - closestAnime = getClosestAnime(searchText.strip(), animeList) - - return closestAnime - - except Exception as e: - print("Error finding anime:{} on MAL\nError:{}".format(searchText, e)) - #traceback.print_exc() - - return None - -#Given a list, it finds the closest anime series it can. -def getClosestAnime(searchText, animeList): - try: - nameList = [] - for anime in animeList: - nameList.append(anime['title'].lower().strip()) - - if anime['english'] is not None: - nameList.append(anime['english'].lower().strip()) - - if anime['synonyms']: - for synonym in anime['synonyms']: - nameList.append(synonym.lower().strip()) - - closestNameFromList = difflib.get_close_matches(searchText.lower(), nameList, cutoff=0.90)[0] - - for anime in animeList: - if anime['title']: - if anime['title'].lower() == closestNameFromList.lower(): - return anime - elif anime['english']: - if anime['english'].lower() == closestNameFromList.lower(): - return anime - else: - for synonym in anime['synonyms']: - if synonym.lower() == closestNameFromList.lower(): - return anime - - return None - except Exception: - #print("Error finding anime:{} on MAL\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - -#MAL's XML is a piece of crap. It needs to be escaped twice because they do shit like this: &sup2; -def convertShittyXML(text): - import html.parser - #It pains me to write shitty code, but MAL needs to improve their API and I'm sick of not being able to parse shit - text = text.replace('É', 'É').replace('×', 'x').replace('’', "'").replace('‘', "'").replace('&hellip', '...').replace('&le', '<').replace('<;', '; ').replace('♥', '♥').replace('—', '-') - text = text.replace('é', 'é').replace('–', '-').replace('Á', 'Á').replace('´', 'à').replace('“', '"').replace('”', '"').replace('Ø', 'Ø').replace('½', '½').replace('∞', '∞') - text = text.replace('à', 'à').replace('è', 'è').replace('†', '†').replace('²', '²').replace(''', "'") - - #text = text.replace('&', '&') - - return text - - - text=html.parser.HTMLParser().unescape(text) - return html.parser.HTMLParser().unescape(text) - -#Used to check if two descriptions are relatively close. This is used in place of author searching because MAL don't give authors at any point. -def getClosestFromDescription(mangaList, descriptionToCheck): - try: - descList = [] - for manga in mangaList: - descList.append(manga['synopsis'].lower()) - - closestNameFromList = difflib.get_close_matches(descriptionToCheck.lower(), descList, 1, 0.1)[0] - - for manga in mangaList: - if closestNameFromList == manga['synopsis'].lower(): - return manga - - except: - return None - -#Since MAL doesn't give me an author, I make a search using similar descriptions instead. Super janky. -async def getMangaCloseToDescription(searchText, descriptionToCheck): - cleanSearchText = urllib.parse.quote(searchText) - try: - try: - async with mal.get('https://myanimelist.net/api/manga/search.xml?q=' + cleanSearchText.rstrip(), timeout=10) as resp: - request = await resp.text() - - except: - setup() - async with mal.get('https://myanimelist.net/api/manga/search.xml?q=' + cleanSearchText.rstrip(), timeout=10) as resp: - request = await resp.text() - - - convertedRequest = convertShittyXML(request) - #print(convertedRequest) - rawList = ET.fromstring(convertedRequest) - - mangaList = [] - - for manga in rawList.findall('./entry'): - mangaId = manga.find('id').text - title = manga.find('title').text - title_english = manga.find('english').text - - synonyms = None - if manga.find('synonyms').text is not None: - synonyms = manga.find('synonyms').text.split(";") - - chapters = manga.find('chapters').text - volumes = manga.find('volumes').text - mangaType = manga.find('type').text - status = manga.find('status').text - start_date = manga.find('start_date').text - end_date = manga.find('end_date').text - synopsis = manga.find('synopsis').text - image = manga.find('image').text - - data = {'id': mangaId, - 'title': title, - 'english': title_english, - 'synonyms': synonyms, - 'chapters': chapters, - 'volumes': volumes, - 'type': mangaType, - 'status': status, - 'start_date': start_date, - 'end_date': end_date, - 'synopsis': synopsis, - 'image': image} - - mangaList.append(data) - - closeManga = getListOfCloseManga(searchText, mangaList) - - return getClosestFromDescription(closeManga, descriptionToCheck) - except: - print("Error finding manga:{} on MAL\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - -async def getLightNovelDetails(searchText, lnId=None): - return await getMangaDetails(searchText, lnId, True) - -#Returns the closest manga series given a specific search term. Again, MAL returns XML, so we conver it ourselves -async def getMangaDetails(searchText, mangaId=None, isLN=False): - cachedManga = DatabaseHandler.checkForMalEntry('malmanga', searchText, mangaId, isLN) - if cachedManga is not None: - if cachedManga['update']: - print("found cached anime, needs update in mal") - pass - else: - print("found cached anime, doesn't need update in mal") - return cachedManga['content'] - cleanSearchText = urllib.parse.quote(searchText) - try: - try: - async with mal.get('https://myanimelist.net/api/manga/search.xml?q=' + cleanSearchText.rstrip(), timeout=10) as resp: - request = await resp.text() - - except Exception as e: - print(e) - setup() - async with mal.get('https://myanimelist.net/api/manga/search.xml?q=' + cleanSearchText.rstrip(), timeout=10) as resp: - request = await resp.text() - - - #convertedRequest = convertShittyXML(request) - rawList = ET.fromstring(request) - #print(convertedRequest) - - mangaList = [] - - for manga in rawList.findall('./entry'): - newMangaId= manga.find('id').text - title = manga.find('title').text - title_english = manga.find('english').text - - synonyms = None - if manga.find('synonyms').text is not None: - synonyms = manga.find('synonyms').text.split(";") - - chapters = manga.find('chapters').text - volumes = manga.find('volumes').text - mangaType = manga.find('type').text - status = manga.find('status').text - start_date = manga.find('start_date').text - end_date = manga.find('end_date').text - synopsis = manga.find('synopsis').text - image = manga.find('image').text - - data = {'id': newMangaId, - 'title': title, - 'english': title_english, - 'synonyms': synonyms, - 'chapters': chapters, - 'volumes': volumes, - 'type': mangaType, - 'status': status, - 'start_date': start_date, - 'end_date': end_date, - 'synopsis': synopsis, - 'image': image } - - #print(data['title']) - #ignore or allow LNs - if 'novel' in mangaType.lower(): - if isLN: - mangaList.append(data) - else: - if not isLN: - mangaList.append(data) - #print(mangaId) - if mangaId: - closestManga = getThingById(mangaId, mangaList) - elif cachedManga and cachedManga['update']: - closestManga = getThingById(cachedManga['id'], mangaList) - else: - closestManga = getClosestManga(searchText.strip(), mangaList) - - if closestManga: - return closestManga - else: - return None - - except Exception as e: - print("Error finding manga:{} on MAL\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - -#Returns a list of manga with titles very close to the search text. Current unused because MAL's API is shit and doesn't return author names. -def getListOfCloseManga(searchText, mangaList): - try: - ratio = 0.90 - returnList = [] - - for manga in mangaList: - if round(difflib.SequenceMatcher(lambda x: x == "", manga['title'].lower(), searchText.lower()).ratio(), 3) >= ratio: - returnList.append(manga) - elif manga['english'] is not None: - if round(difflib.SequenceMatcher(lambda x: x == "", manga['english'].lower(), searchText.lower()).ratio(), 3) >= ratio: - returnList.append(manga) - elif manga['synonyms'] is not None: - for synonym in manga['synonyms']: - if round(difflib.SequenceMatcher(lambda x: x == "", synonym, searchText.lower()).ratio(), 3) >= ratio: - returnList.append(manga) - break - - return returnList - - except Exception: - traceback.print_exc() - return None - -#Used to determine the closest manga to a given search term in a list -def getClosestManga(searchText, mangaList): - try: - nameList = [] - - for manga in mangaList: - nameList.append(manga['title'].lower().strip()) - - if manga['english'] is not None: - nameList.append(manga['english'].lower().strip()) - - if manga['synonyms'] is not None: - for synonym in manga['synonyms']: - nameList.append(synonym.lower().strip()) - #print(searchText) - closestNameFromList = difflib.get_close_matches(searchText.lower().strip(), nameList,1, 0.90)[0] - #print(closestNameFromList) - for manga in mangaList: - if manga['title'].lower() == closestNameFromList.lower(): - return manga - elif manga['english'] is not None: - if manga['english'].lower() == closestNameFromList.lower(): - return manga - - for manga in mangaList: - if manga['synonyms'] is not None: - for synonym in manga['synonyms']: - if synonym.lower().strip() == closestNameFromList.lower(): - return manga - - return None - except Exception as e: - #print("Error finding manga:{} on MAL\nError:{}".format(searchText, e)) - #traceback.print_exc() - return None - - -#Used to find thing by an id -def getThingById(thingId, thingList): - try: - for thing in thingList: - if int(thing['id']) == int(thingId): - return thing - - return None - except Exception: - traceback.print_exc() - return None - - -setup() diff --git a/roboragi_old/MU.py b/roboragi_old/MU.py deleted file mode 100644 index 26cda6f..0000000 --- a/roboragi_old/MU.py +++ /dev/null @@ -1,145 +0,0 @@ -''' -MU.py -Handles all MangaUpdates information -''' - -from pyquery import PyQuery as pq -import aiohttp -import difflib -import traceback -import pprint -import collections - -req = aiohttp.ClientSession() - -def findClosestManga(searchText, mangaList): - try: - nameList = [] - - for manga in mangaList: - nameList.append(manga['title'].lower()) - - closestNameFromList = difflib.get_close_matches(searchText.lower(), nameList, 1, 0.85) - - for manga in mangaList: - if manga['title'].lower() == closestNameFromList[0].lower(): - return manga - - return None - except: - return None - -async def findAuthorURL(authorName): - try: - payload = {'search': authorName} - async with req.get('https://mangaupdates.com/authors.html', params=payload, timeout=10) as resp: - html = await resp.text() - - mu = pq(html) - authorURL = None - - for thing in pq(mu).find('table tr td .text .pad'): - try: - url = pq(thing).find('a').attr('href') - if 'http://www.mangaupdates.com/authors.html?id=' in url: - authorURL = url - except: - pass - - return authorURL - except: - - traceback.print_exc() - return None - -async def findSeriesURLViaAuthor(seriesName, authorName, authorURL): - try: - async with req.get(authorURL, timeout=10) as resp: - html = await resp.text() - - mu = pq(html) - authorURL = None - - authorName = authorName.lower() - authorName = authorName.split(' ') - - for thing in mu.find('table tr .text'): - try: - title = pq(thing).find('a')[1].text - url = pq(thing).find('a').attr('href') - - if url: - if 'http://www.mangaupdates.com/series.html?id=' in url: - title = title.lower() - - for name in authorName: - title = title.replace(name, '') - - s = difflib.SequenceMatcher(lambda x: x == "", seriesName, title) - if s.ratio() > 0.5: - return url - - except: - pass - - return authorURL - except: - - traceback.print_exc() - return None - -async def getMangaWithAuthor(searchText, authorName): - try: - url = await findAuthorURL(authorName) - - if not url: - rearrangedAuthorNames = collections.deque(authorName.split(' ')) - rearrangedAuthorNames.rotate(-1) - rearrangedName = ' '.join(rearrangedAuthorNames) - url = await findAuthorURL(rearrangedName) - - if url: - return await findSeriesURLViaAuthor(searchText, authorName, url) - else: - return None - - except: - traceback.print_exc() - return None - -async def getMangaURL(searchText): - try: - payload = {'search': searchText} - async with req.get('https://mangaupdates.com/series.html', params=payload, timeout=10) as resp: - html = await resp.text() - - - mu = pq(html) - - mangaList = [] - - for thing in mu.find('.series_rows_table tr'): - title = pq(thing).find('.col1').text() - url = pq(thing).find('.col1 a').attr('href') - genres = pq(thing).find('.col2').text() - year = pq(thing).find('.col3').text() - rating = pq(thing).find('.col4').text() - - if title: - data = { 'title': title, - 'url': url, - 'genres': genres, - 'year': year, - 'rating': rating } - - mangaList.append(data) - - closest = findClosestManga(searchText, mangaList) - return closest['url'] - - except: - - return None - -def getMangaURLById(mangaId): - return 'https://www.mangaupdates.com/series.html?id=' + str(mangaId) diff --git a/roboragi_old/NU.py b/roboragi_old/NU.py deleted file mode 100644 index 06be551..0000000 --- a/roboragi_old/NU.py +++ /dev/null @@ -1,71 +0,0 @@ -''' -NovelUpdates.py -Handles all NovelUpdates information -''' - -from pyquery import PyQuery as pq -import aiohttp -import difflib -import traceback -import pprint -import collections - -req = aiohttp.ClientSession() - -async def getLightNovelURL(searchText): - try: - searchText = searchText.replace(' ', '+') - async with req.get('http://www.novelupdates.com/?s=' + searchText, timeout=10) as resp: - html = await resp.text() - - - nu = pq(html) - - lnList = [] - - for thing in nu.find('.w-blog-entry'): - title = pq(thing).find('.w-blog-entry-title').text() - url = pq(thing).find('.w-blog-entry-link').attr('href') - - if title: - data = { 'title': title, - 'url': url } - lnList.append(data) - - closest = findClosestLightNovel(searchText, lnList) - return closest['url'] - - except: - - return None - -def findClosestLightNovel(searchText, lnList): - try: - nameList = [] - nameListWithoutWN = [] - - for ln in lnList: - nameList.append(ln['title'].lower()) - - if '(wn)' not in ln['title'].lower(): - nameListWithoutWN.append(ln['title'].lower()) - - - closestNameFromListWithoutWN = difflib.get_close_matches(searchText.lower(), nameListWithoutWN, 1, 0.80) - closestNameFromListWithWN = difflib.get_close_matches(searchText.lower(), nameList, 1, 0.80) - - if closestNameFromListWithoutWN: - nameToUse = closestNameFromListWithoutWN[0].lower() - else: - nameToUse = closestNameFromListWithWN[0].lower() - - for ln in lnList: - if ln['title'].lower() == nameToUse: - return ln - - return None - except: - return None - -def getLightNovelById(lnId): - return 'http://www.novelupdates.com/series/' + str(lnId) diff --git a/roboragi_old/PreCache.py b/roboragi_old/PreCache.py deleted file mode 100644 index eb1df84..0000000 --- a/roboragi_old/PreCache.py +++ /dev/null @@ -1,82 +0,0 @@ -import aiohttp -import asyncio -import DatabaseHandler -import Anilist -import MAL -import traceback -import urllib -import math - - -async def setup(): - end_index = input("How many anime titles do you want? ") - #result = await top_n_by_popularity('anime', end_index) - result2 = await top_n_by_popularity('manga', end_index) - result3 = await top40ByGenre('manga') - -async def top40ByGenre(medium): - errorList = [] - genres = await Anilist.getGenres(medium) - for entry in genres: - top40 = await Anilist.GetTop40ByGenre(medium, entry['genre']) - for entry in top40: - print("Working on anilist id: {}".format(entry['id'])) - try: - DatabaseHandler.PopulateCache('anilist{}'.format(medium), entry) - except Exception as e: - print("{} failed with exception {}".format(entry['id'], e)) - try: - animeName = None - if entry['title_romaji']: - animeName = entry['title_romaji'] - else: - animeName = entry['title_english'] - if medium == 'anime': - malanime = await MAL.getAnimeDetails(animeName) - elif medium == 'manga': - malanime = await MAL.getMangaDetails(animeName) - if malanime: - try: - DatabaseHandler.PopulateCache('mal{}'.format(medium), malanime) - except Exception as e: - print("{} failed with exception {}".format(malanime['id'], e)) - except Exception as e: - print("debug 1 error: {}".format(e)) - -async def top_n_by_popularity(medium, n): - count = 1 - final_page = math.ceil(float(n)/float(40)) - while count < final_page: - try: - print("\n\n-------------Starting page {}------------\n\n".format(count)) - page_entries = await Anilist.get_page_by_popularity(medium, count) - for entry in page_entries: - print("Working on anilist id: {}".format(entry['id'])) - try: - DatabaseHandler.PopulateCache('anilist{}'.format(medium), entry) - except Exception as e: - print("{} failed with exception {}\n".format(entry['id'], e)) - try: - animeName = None - if entry['title_romaji']: - animeName = entry['title_romaji'] - else: - animeName = entry['title_english'] - if medium == 'anime': - malanime = await MAL.getAnimeDetails(animeName) - elif medium == 'manga': - malanime = await MAL.getMangaDetails(animeName) - if malanime: - try: - DatabaseHandler.PopulateCache('mal{}'.format(medium), malanime) - except Exception as e: - print("{} failed with exception {}\n".format(malanime['id'], e)) - except Exception as e: - print("debug 1 error: {}\n".format(e)) - count +=1 - except Exception as e: - count +=1 - print(e) - -loop = asyncio.get_event_loop() -loop.run_until_complete(setup()) diff --git a/roboragi_old/Reference.py b/roboragi_old/Reference.py deleted file mode 100644 index 3c6bd98..0000000 --- a/roboragi_old/Reference.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- - -import sqlite3 - -sqlConn = sqlite3.connect('reference.db') -sqlCur = sqlConn.cursor() - -def is_april_fools_2016(username): - try: - sqlCur.execute("SELECT 1 FROM aprilfools2016 WHERE username = ? LIMIT 1", [username]) - result = sqlCur.fetchone() - - if result: - return True - else: - return False - except Exception as e: - return False - -def get_bling(username): - if is_april_fools_2016(username): - return ' ^(| \U0001F4B0)' - else: - return '' diff --git a/roboragi_old/Wikipedia.py b/roboragi_old/Wikipedia.py deleted file mode 100644 index 2b20012..0000000 --- a/roboragi_old/Wikipedia.py +++ /dev/null @@ -1,62 +0,0 @@ -import requests -import difflib -import pprint -from urllib.parse import quote - -BASE_RESULT_URL = 'https://en.wikipedia.org/wiki/' -BASE_API_URL = 'https://en.wikipedia.org/w/api.php?' - -wiki = requests.Session() -wiki.headers.update({'User-Agent': 'Roboragi - An Anime/Manga Reddit Bot - Contact /u/Nihilate on Reddit'}) - -def getAnimeURL(searchText): - return getThingURL(searchText, 'Anime') - -def getMangaURL(searchText): - return getThingURL(searchText, 'Manga') - -def getThingURL(searchText, searchType=None): - try: - request = wiki.get(BASE_API_URL + 'action=query&format=json&list=search&utf8=1&srsearch=' + searchText, timeout=10) - except: - return None - - result = request.json() - - pprint.pprint(result) - - thingTitles = [] - - for thing in result['query']['search']: - #bloody disambiguation - if 'can refer to' in thing['snippet']: - continue - - if searchType: - if searchType.lower() in thing['snippet']: - thingTitles.append(thing['title']) - else: - thingTitles.append(thing['title']) - - print(thingTitles) - - closestThings = difflib.get_close_matches(searchText.lower(), [title.lower() for title in thingTitles], 1, 0.90) - - if closestThings: - for title in thingTitles: - if closestThings[0].lower() in title.lower(): - return BASE_RESULT_URL + quote(title) - else: - if thingTitles: - for thing in result['query']['search']: - if thing['title'].lower() in thingTitles[0].lower(): - if (searchText.lower() in thing['snippet'].lower()): - return BASE_RESULT_URL + quote(thingTitles[0]) - break - - return None - -def getThingURLById(thingId): - return BASE_RESULT_URL + quote(thingId) - -print(getAnimeURL('monogatari series')) diff --git a/roboragi_old/reference.db b/roboragi_old/reference.db deleted file mode 100644 index 3ec9695..0000000 Binary files a/roboragi_old/reference.db and /dev/null differ diff --git a/roboragi_old/synonyms.db b/roboragi_old/synonyms.db deleted file mode 100644 index c8bea7f..0000000 Binary files a/roboragi_old/synonyms.db and /dev/null differ