aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Senjan21 <[email protected]>2022-05-09 18:21:21 +0200
committerGravatar GitHub <[email protected]>2022-05-09 18:21:21 +0200
commit019983c3785191a0c7182c62394cec2bac123d51 (patch)
tree54c296bd04a13a0097cbd7249b78a6716556d735
parentDoublefixed indentation and removed unused import. (diff)
parentBump pillow from 9.0.0 to 9.0.1 (#1045) (diff)
Merge branch 'main' into uwu
-rw-r--r--.github/CODEOWNERS10
-rw-r--r--.github/workflows/lint.yaml19
-rw-r--r--.gitignore2
-rw-r--r--.gitpod.yml5
-rw-r--r--.pre-commit-config.yaml5
-rw-r--r--Dockerfile3
-rwxr-xr-xREADME.md1
-rw-r--r--bot/__init__.py91
-rw-r--r--bot/__main__.py23
-rw-r--r--bot/command.py18
-rw-r--r--bot/constants.py49
-rw-r--r--bot/exts/avatar_modification/avatar_modify.py4
-rw-r--r--bot/exts/core/error_handler.py13
-rw-r--r--bot/exts/core/extensions.py4
-rw-r--r--bot/exts/core/help.py5
-rw-r--r--bot/exts/core/internal_eval/_internal_eval.py12
-rw-r--r--bot/exts/core/source.py4
-rw-r--r--bot/exts/events/advent_of_code/_cog.py342
-rw-r--r--bot/exts/events/advent_of_code/_helpers.py87
-rw-r--r--bot/exts/events/advent_of_code/views/dayandstarview.py82
-rw-r--r--bot/exts/events/hacktoberfest/hacktober-issue-finder.py11
-rw-r--r--bot/exts/events/trivianight/__init__.py0
-rw-r--r--bot/exts/events/trivianight/_game.py192
-rw-r--r--bot/exts/events/trivianight/_questions.py179
-rw-r--r--bot/exts/events/trivianight/_scoreboard.py186
-rw-r--r--bot/exts/events/trivianight/trivianight.py328
-rw-r--r--bot/exts/fun/anagram.py109
-rw-r--r--bot/exts/fun/battleship.py1
-rw-r--r--bot/exts/fun/connect_four.py3
-rw-r--r--bot/exts/fun/duck_game.py42
-rw-r--r--bot/exts/fun/game.py32
-rw-r--r--bot/exts/fun/latex.py130
-rw-r--r--bot/exts/fun/madlibs.py148
-rw-r--r--bot/exts/fun/quack.py75
-rw-r--r--bot/exts/fun/snakes/_utils.py25
-rw-r--r--bot/exts/fun/tic_tac_toe.py19
-rw-r--r--bot/exts/fun/trivia_quiz.py6
-rw-r--r--bot/exts/holidays/easter/earth_photos.py3
-rw-r--r--bot/exts/holidays/easter/egg_facts.py2
-rw-r--r--bot/exts/holidays/halloween/candy_collection.py24
-rw-r--r--bot/exts/holidays/halloween/scarymovie.py1
-rw-r--r--bot/exts/holidays/halloween/spookynamerate.py14
-rw-r--r--bot/exts/holidays/halloween/spookyreact.py8
-rw-r--r--bot/exts/holidays/hanukkah/hanukkah_embed.py84
-rw-r--r--bot/exts/holidays/pride/pride_facts.py2
-rw-r--r--bot/exts/holidays/pride/pride_leader.py2
-rw-r--r--bot/exts/holidays/valentines/be_my_valentine.py42
-rw-r--r--bot/exts/holidays/valentines/lovecalculator.py11
-rw-r--r--bot/exts/utilities/bookmark.py13
-rw-r--r--bot/exts/utilities/challenges.py341
-rw-r--r--bot/exts/utilities/colour.py266
-rw-r--r--bot/exts/utilities/conversationstarters.py91
-rw-r--r--bot/exts/utilities/emoji.py6
-rw-r--r--bot/exts/utilities/epoch.py138
-rw-r--r--bot/exts/utilities/githubinfo.py220
-rw-r--r--bot/exts/utilities/issues.py275
-rw-r--r--bot/exts/utilities/latex.py101
-rw-r--r--bot/exts/utilities/realpython.py16
-rw-r--r--bot/exts/utilities/reddit.py10
-rw-r--r--bot/exts/utilities/twemoji.py150
-rw-r--r--bot/exts/utilities/wikipedia.py6
-rw-r--r--bot/exts/utilities/wtf_python.py138
-rw-r--r--bot/group.py18
-rw-r--r--bot/log.py96
-rw-r--r--bot/monkey_patches.py91
-rw-r--r--bot/resources/fun/anagram.json17668
-rw-r--r--bot/resources/fun/latex_template.txt5
-rw-r--r--bot/resources/fun/madlibs_templates.json135
-rw-r--r--bot/resources/fun/trivia_quiz.json2
-rw-r--r--bot/resources/holidays/halloween/bat-clipart.pngbin12313 -> 19006 bytes
-rw-r--r--bot/resources/utilities/py_topics.yaml36
-rw-r--r--bot/resources/utilities/ryanzec_colours.json1569
-rw-r--r--bot/resources/utilities/starter.yaml3
-rw-r--r--bot/resources/utilities/wtf_python_logo.jpgbin0 -> 19481 bytes
-rw-r--r--bot/utils/checks.py11
-rw-r--r--bot/utils/decorators.py31
-rw-r--r--bot/utils/exceptions.py7
-rw-r--r--bot/utils/halloween/spookifications.py3
-rw-r--r--bot/utils/members.py47
-rw-r--r--bot/utils/pagination.py10
-rw-r--r--docker-compose.yml1
-rw-r--r--poetry.lock1207
-rw-r--r--pyproject.toml22
-rw-r--r--sir-lancebot-logo.pngbin65083 -> 122287 bytes
-rw-r--r--tox.ini10
85 files changed, 23811 insertions, 1390 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index cffe15d5..d164ad04 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -4,10 +4,6 @@ bot/exts/events/hacktoberfest/** @ks129
bot/exts/holidays/halloween/** @ks129
# CI & Docker
-.github/workflows/** @Akarys42 @SebastiaanZ @Den4200
-Dockerfile @Akarys42 @Den4200
-docker-compose.yml @Akarys42 @Den4200
-
-# Tools
-poetry.lock @Akarys42
-pyproject.toml @Akarys42
+.github/workflows/** @SebastiaanZ @Den4200
+Dockerfile @Den4200
+docker-compose.yml @Den4200
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 81706e1e..2cbfc2f5 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -12,7 +12,7 @@ concurrency:
jobs:
lint:
- name: Run pre-commit & flake8
+ name: Run linting & tests
runs-on: ubuntu-latest
env:
# List of licenses that are compatible with the MIT License and
@@ -26,6 +26,9 @@ jobs:
Mozilla Public License 2.0 (MPL 2.0);
Public Domain;
Python Software Foundation License
+ # See https://github.com/pre-commit/pre-commit/issues/2178#issuecomment-1002163763
+ # for why we set this.
+ SETUPTOOLS_USE_DISTUTILS: stdlib
# Configure pip to cache dependencies and do a user install
PIP_NO_CACHE_DIR: false
@@ -74,12 +77,22 @@ jobs:
pip install poetry
poetry install --no-interaction --no-ansi
- # Check all the dependencies are compatible with the MIT license.
+ # Check all of our dev dependencies are compatible with the MIT license.
# If you added a new dependencies that is being rejected,
# please make sure it is compatible with the license for this project,
# and add it to the ALLOWED_LICENSE variable
- name: Check Dependencies License
- run: pip-licenses --allow-only="$ALLOWED_LICENSES"
+ run: |
+ pip-licenses --allow-only="$ALLOWED_LICENSE" \
+ --package $(poetry export -f requirements.txt --without-hashes | sed "s/==.*//g" | tr "\n" " ")
+
+ # Attempt to run the bot. Setting `IN_CI` to true, so bot.run() is never called.
+ # This is to catch import and cog setup errors that may appear in PRs, to avoid crash loops if merged.
+ - name: Attempt bot setup
+ run: "python -m bot"
+ env:
+ USE_FAKEREDIS: true
+ IN_CI: true
# This step caches our pre-commit environment. To make sure we
# do create a new environment when our pre-commit setup changes,
diff --git a/.gitignore b/.gitignore
index ce122d29..665df8cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,7 @@
# bot (project-specific)
log/*
data/*
-_latex_cache/*
+bot/exts/fun/_latex_cache/*
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 00000000..a10e6e26
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,5 @@
+tasks:
+ - name: "Python Environment"
+ before: "pyenv install 3.9.6 && pyenv global 3.9.6"
+ init: "pip install poetry && export PIP_USER=false"
+ command: "poetry install && poetry run pre-commit install"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7244cb4e..2131db72 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,6 +12,11 @@ repos:
rev: v1.5.1
hooks:
- id: python-check-blanket-noqa
+ - repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+ name: isort (python)
- repo: local
hooks:
- id: flake8
diff --git a/Dockerfile b/Dockerfile
index 0b5876bd..44ef0574 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,6 +25,3 @@ COPY . .
# Start the bot
CMD ["python", "-m", "bot"]
-
-# Define docker persistent volumes
-VOLUME /bot/bot/log /bot/data
diff --git a/README.md b/README.md
index dd8301dc..2e2b7aec 100755
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
[![Lint Badge][1]][2]
[![Build Badge][3]][4]
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
+[![Open in Gitpod](https://img.shields.io/badge/Gitpod-ready--to--code-908a85?logo=gitpod)](https://gitpod.io/#/github.com/python-discord/sir-lancebot)
![Header](sir-lancebot-logo.png)
diff --git a/bot/__init__.py b/bot/__init__.py
index c6a48105..3136c863 100644
--- a/bot/__init__.py
+++ b/bot/__init__.py
@@ -7,88 +7,51 @@ except ModuleNotFoundError:
import asyncio
import logging
-import logging.handlers
import os
from functools import partial, partialmethod
-from pathlib import Path
import arrow
+import sentry_sdk
from discord.ext import commands
+from sentry_sdk.integrations.logging import LoggingIntegration
+from sentry_sdk.integrations.redis import RedisIntegration
-from bot.command import Command
-from bot.constants import Client
-from bot.group import Group
+from bot import log, monkey_patches
+sentry_logging = LoggingIntegration(
+ level=logging.DEBUG,
+ event_level=logging.WARNING
+)
-# Configure the "TRACE" logging level (e.g. "log.trace(message)")
-logging.TRACE = 5
-logging.addLevelName(logging.TRACE, "TRACE")
-
-
-def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None:
- """
- Log 'msg % args' with severity 'TRACE'.
-
- To pass exception information, use the keyword argument exc_info with a true value, e.g.
- logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
- """
- if self.isEnabledFor(logging.TRACE):
- self._log(logging.TRACE, msg, args, **kwargs)
-
+sentry_sdk.init(
+ dsn=os.environ.get("BOT_SENTRY_DSN"),
+ integrations=[
+ sentry_logging,
+ RedisIntegration()
+ ],
+ release=f"sir-lancebot@{os.environ.get('GIT_SHA', 'foobar')}"
+)
-logging.Logger.trace = monkeypatch_trace
+log.setup()
# Set timestamp of when execution started (approximately)
start_time = arrow.utcnow()
-# Set up file logging
-log_dir = Path("bot/log")
-log_file = log_dir / "hackbot.log"
-os.makedirs(log_dir, exist_ok=True)
-
-# File handler rotates logs every 5 MB
-file_handler = logging.handlers.RotatingFileHandler(
- log_file, maxBytes=5 * (2**20), backupCount=10, encoding="utf-8",
-)
-file_handler.setLevel(logging.TRACE if Client.debug else logging.DEBUG)
-
-# Console handler prints to terminal
-console_handler = logging.StreamHandler()
-level = logging.TRACE if Client.debug else logging.INFO
-console_handler.setLevel(level)
-
-# Remove old loggers, if any
-root = logging.getLogger()
-if root.handlers:
- for handler in root.handlers:
- root.removeHandler(handler)
-
-# Silence irrelevant loggers
-logging.getLogger("discord").setLevel(logging.ERROR)
-logging.getLogger("websockets").setLevel(logging.ERROR)
-logging.getLogger("PIL").setLevel(logging.ERROR)
-logging.getLogger("matplotlib").setLevel(logging.ERROR)
-logging.getLogger("async_rediscache").setLevel(logging.WARNING)
-
-# Setup new logging configuration
-logging.basicConfig(
- format="%(asctime)s - %(name)s %(levelname)s: %(message)s",
- datefmt="%D %H:%M:%S",
- level=logging.TRACE if Client.debug else logging.DEBUG,
- handlers=[console_handler, file_handler],
-)
-logging.getLogger().info("Logging initialization complete")
-
-
# On Windows, the selector event loop is required for aiodns.
if os.name == "nt":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+monkey_patches.patch_typing()
+
+# This patches any convertors that use PartialMessage, but not the PartialMessageConverter itself
+# as library objects are made by this mapping.
+# https://github.com/Rapptz/discord.py/blob/1a4e73d59932cdbe7bf2c281f25e32529fc7ae1f/discord/ext/commands/converter.py#L984-L1004
+commands.converter.PartialMessageConverter = monkey_patches.FixedPartialMessageConverter
# Monkey-patch discord.py decorators to use the both the Command and Group subclasses which supports root aliases.
# Must be patched before any cogs are added.
-commands.command = partial(commands.command, cls=Command)
-commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command)
+commands.command = partial(commands.command, cls=monkey_patches.Command)
+commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=monkey_patches.Command)
-commands.group = partial(commands.group, cls=Group)
-commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=Group)
+commands.group = partial(commands.group, cls=monkey_patches.Group)
+commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=monkey_patches.Group)
diff --git a/bot/__main__.py b/bot/__main__.py
index c6e5fa57..0bf7b398 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -1,28 +1,10 @@
import logging
-import sentry_sdk
-from sentry_sdk.integrations.logging import LoggingIntegration
-from sentry_sdk.integrations.redis import RedisIntegration
-
from bot.bot import bot
-from bot.constants import Client, GIT_SHA, STAFF_ROLES, WHITELISTED_CHANNELS
+from bot.constants import Client, STAFF_ROLES, WHITELISTED_CHANNELS
from bot.utils.decorators import whitelist_check
from bot.utils.extensions import walk_extensions
-sentry_logging = LoggingIntegration(
- level=logging.DEBUG,
- event_level=logging.WARNING
-)
-
-sentry_sdk.init(
- dsn=Client.sentry_dsn,
- integrations=[
- sentry_logging,
- RedisIntegration()
- ],
- release=f"sir-lancebot@{GIT_SHA}"
-)
-
log = logging.getLogger(__name__)
bot.add_check(whitelist_check(channels=WHITELISTED_CHANNELS, roles=STAFF_ROLES))
@@ -30,4 +12,5 @@ bot.add_check(whitelist_check(channels=WHITELISTED_CHANNELS, roles=STAFF_ROLES))
for ext in walk_extensions():
bot.load_extension(ext)
-bot.run(Client.token)
+if not Client.in_ci:
+ bot.run(Client.token)
diff --git a/bot/command.py b/bot/command.py
deleted file mode 100644
index 0fb900f7..00000000
--- a/bot/command.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from discord.ext import commands
-
-
-class Command(commands.Command):
- """
- A `discord.ext.commands.Command` subclass which supports root aliases.
-
- A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as
- top-level commands rather than being aliases of the command's group. It's stored as an attribute
- also named `root_aliases`.
- """
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.root_aliases = kwargs.get("root_aliases", [])
-
- if not isinstance(self.root_aliases, (list, tuple)):
- raise TypeError("Root aliases of a command must be a list or a tuple of strings.")
diff --git a/bot/constants.py b/bot/constants.py
index 2313bfdb..da81a089 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -12,6 +12,7 @@ __all__ = (
"Channels",
"Categories",
"Client",
+ "Logging",
"Colours",
"Emojis",
"Icons",
@@ -23,6 +24,7 @@ __all__ = (
"Reddit",
"RedisConfig",
"RedirectOutput",
+ "PYTHON_PREFIX",
"MODERATION_ROLES",
"STAFF_ROLES",
"WHITELISTED_CHANNELS",
@@ -34,6 +36,9 @@ __all__ = (
log = logging.getLogger(__name__)
+PYTHON_PREFIX = "!"
+
+
@dataclasses.dataclass
class AdventOfCodeLeaderboard:
id: str
@@ -50,7 +55,7 @@ class AdventOfCodeLeaderboard:
def session(self) -> str:
"""Return either the actual `session` cookie or the fallback cookie."""
if self.use_fallback_session:
- log.info(f"Returning fallback cookie for board `{self.id}`.")
+ log.trace(f"Returning fallback cookie for board `{self.id}`.")
return AdventOfCode.fallback_session
return self._session
@@ -88,6 +93,7 @@ class AdventOfCode:
ignored_days = environ.get("AOC_IGNORED_DAYS", "").split(",")
leaderboard_displayed_members = 10
leaderboard_cache_expiry_seconds = 1800
+ max_day_and_star_results = 15
year = int(environ.get("AOC_YEAR", datetime.utcnow().year))
role_id = int(environ.get("AOC_ROLE_ID", 518565788744024082))
@@ -101,9 +107,10 @@ class Cats:
class Channels(NamedTuple):
- advent_of_code = int(environ.get("AOC_CHANNEL_ID", 782715290437943306))
- advent_of_code_commands = int(environ.get("AOC_COMMANDS_CHANNEL_ID", 607247579608121354))
- bot = 267659945086812160
+ advent_of_code = int(environ.get("AOC_CHANNEL_ID", 897932085766004786))
+ advent_of_code_commands = int(environ.get("AOC_COMMANDS_CHANNEL_ID", 897932607545823342))
+ bot_commands = 267659945086812160
+ community_meta = 267659945086812160
organisation = 551789653284356126
devlog = int(environ.get("CHANNEL_DEVLOG", 622895325144940554))
dev_contrib = 635950537262759947
@@ -112,7 +119,7 @@ class Channels(NamedTuple):
off_topic_0 = 291284109232308226
off_topic_1 = 463035241142026251
off_topic_2 = 463035268514185226
- community_bot_commands = int(environ.get("CHANNEL_COMMUNITY_BOT_COMMANDS", 607247579608121354))
+ sir_lancebot_playground = int(environ.get("CHANNEL_COMMUNITY_BOT_COMMANDS", 607247579608121354))
voice_chat_0 = 412357430186344448
voice_chat_1 = 799647045886541885
staff_voice = 541638762007101470
@@ -126,22 +133,31 @@ class Categories(NamedTuple):
media = 799054581991997460
staff = 364918151625965579
+
codejam_categories_name = "Code Jam" # Name of the codejam team categories
+
class Client(NamedTuple):
name = "Sir Lancebot"
guild = int(environ.get("BOT_GUILD", 267624335836053506))
prefix = environ.get("PREFIX", ".")
token = environ.get("BOT_TOKEN")
- sentry_dsn = environ.get("BOT_SENTRY_DSN")
debug = environ.get("BOT_DEBUG", "true").lower() == "true"
+ in_ci = environ.get("IN_CI", "false").lower() == "true"
github_bot_repo = "https://github.com/python-discord/sir-lancebot"
# Override seasonal locks: 1 (January) to 12 (December)
month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None
+class Logging(NamedTuple):
+ debug = Client.debug
+ file_logs = environ.get("FILE_LOGS", "false").lower() == "true"
+ trace_loggers = environ.get("BOT_TRACE_LOGGERS")
+
+
class Colours:
blue = 0x0279FD
+ twitter_blue = 0x1DA1F2
bright_green = 0x01D277
dark_green = 0x1F8B4C
orange = 0xE67E22
@@ -192,7 +208,7 @@ class Emojis:
# These icons are from Github's repo https://github.com/primer/octicons/
issue_open = "<:IssueOpen:852596024777506817>"
- issue_closed = "<:IssueClosed:852596024739758081>"
+ issue_closed = "<:IssueClosed:927326162861039626>"
issue_draft = "<:IssueDraft:852596025147523102>" # Not currently used by Github, but here for future.
pull_request_open = "<:PROpen:852596471505223781>"
pull_request_closed = "<:PRClosed:852596024732286976>"
@@ -226,7 +242,6 @@ class Emojis:
status_dnd = "<:status_dnd:470326272082313216>"
status_offline = "<:status_offline:470326266537705472>"
-
stackoverflow_tag = "<:stack_tag:870926975307501570>"
stackoverflow_views = "<:stack_eye:870926992692879371>"
@@ -280,11 +295,13 @@ if Client.month_override is not None:
class Roles(NamedTuple):
- admin = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896))
- moderator = 267629731250176001
- owner = 267627879762755584
+ owners = 267627879762755584
+ admins = int(environ.get("BOT_ADMIN_ROLE_ID", 267628507062992896))
+ moderation_team = 267629731250176001
helpers = int(environ.get("ROLE_HELPERS", 267630620367257601))
core_developers = 587606783669829632
+ everyone = int(environ.get("BOT_GUILD", 267624335836053506))
+ aoc_completionist = int(environ.get("AOC_COMPLETIONIST_ROLE_ID", 916691790181056532))
class Tokens(NamedTuple):
@@ -331,13 +348,13 @@ class Reddit:
# Default role combinations
-MODERATION_ROLES = Roles.moderator, Roles.admin, Roles.owner
-STAFF_ROLES = Roles.helpers, Roles.moderator, Roles.admin, Roles.owner
+MODERATION_ROLES = {Roles.moderation_team, Roles.admins, Roles.owners}
+STAFF_ROLES = {Roles.helpers, Roles.moderation_team, Roles.admins, Roles.owners}
# Whitelisted channels
WHITELISTED_CHANNELS = (
- Channels.bot,
- Channels.community_bot_commands,
+ Channels.bot_commands,
+ Channels.sir_lancebot_playground,
Channels.off_topic_0,
Channels.off_topic_1,
Channels.off_topic_2,
@@ -345,8 +362,6 @@ WHITELISTED_CHANNELS = (
Channels.voice_chat_1,
)
-GIT_SHA = environ.get("GIT_SHA", "foobar")
-
# Bot replies
ERROR_REPLIES = [
"Please don't do that.",
diff --git a/bot/exts/avatar_modification/avatar_modify.py b/bot/exts/avatar_modification/avatar_modify.py
index 87eb05e6..3ee70cfd 100644
--- a/bot/exts/avatar_modification/avatar_modify.py
+++ b/bot/exts/avatar_modification/avatar_modify.py
@@ -239,7 +239,7 @@ class AvatarModify(commands.Cog):
description=f"Here is your lovely avatar, surrounded by\n a beautiful {option} flag. Enjoy :D"
)
embed.set_image(url=f"attachment://{file_name}")
- embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.avatar.url)
+ embed.set_footer(text=f"Made by {ctx.author.display_name}.", icon_url=ctx.author.display_avatar.url)
await ctx.send(file=file, embed=embed)
@avatar_modify.group(
@@ -286,7 +286,7 @@ class AvatarModify(commands.Cog):
@avatar_modify.command(
aliases=("savatar", "spookify"),
root_aliases=("spookyavatar", "spookify", "savatar"),
- brief="Spookify an user's avatar."
+ brief="Spookify a user's avatar."
)
async def spookyavatar(self, ctx: commands.Context) -> None:
"""This "spookifies" the user's avatar, with a random *spooky* effect."""
diff --git a/bot/exts/core/error_handler.py b/bot/exts/core/error_handler.py
index fd2123e7..983632ba 100644
--- a/bot/exts/core/error_handler.py
+++ b/bot/exts/core/error_handler.py
@@ -12,7 +12,7 @@ from sentry_sdk import push_scope
from bot.bot import Bot
from bot.constants import Channels, Colours, ERROR_REPLIES, NEGATIVE_REPLIES, RedirectOutput
from bot.utils.decorators import InChannelCheckFailure, InMonthCheckFailure
-from bot.utils.exceptions import APIError, UserNotPlayingError
+from bot.utils.exceptions import APIError, MovedCommandError, UserNotPlayingError
log = logging.getLogger(__name__)
@@ -98,7 +98,8 @@ class CommandErrorHandler(commands.Cog):
if isinstance(error, commands.NoPrivateMessage):
await ctx.send(
embed=self.error_embed(
- f"This command can only be used in the server. Go to <#{Channels.community_bot_commands}> instead!",
+ "This command can only be used in the server. "
+ f"Go to <#{Channels.sir_lancebot_playground}> instead!",
NEGATIVE_REPLIES
)
)
@@ -130,6 +131,14 @@ class CommandErrorHandler(commands.Cog):
)
return
+ if isinstance(error, MovedCommandError):
+ description = (
+ f"This command, `{ctx.prefix}{ctx.command.qualified_name}` has moved to `{error.new_command_name}`.\n"
+ f"Please use `{error.new_command_name}` instead."
+ )
+ await ctx.send(embed=self.error_embed(description, NEGATIVE_REPLIES))
+ return
+
with push_scope() as scope:
scope.user = {
"id": ctx.author.id,
diff --git a/bot/exts/core/extensions.py b/bot/exts/core/extensions.py
index 424bacac..d809d2b9 100644
--- a/bot/exts/core/extensions.py
+++ b/bot/exts/core/extensions.py
@@ -18,7 +18,7 @@ from bot.utils.pagination import LinePaginator
log = logging.getLogger(__name__)
-UNLOAD_BLACKLIST = {f"{exts.__name__}.utils.extensions"}
+UNLOAD_BLACKLIST = {f"{exts.__name__}.core.extensions"}
BASE_PATH_LEN = len(exts.__name__.split("."))
@@ -152,7 +152,7 @@ class Extensions(commands.Cog):
Grey indicates that the extension is unloaded.
Green indicates that the extension is currently loaded.
"""
- embed = Embed(colour=Colour.blurple())
+ embed = Embed(colour=Colour.og_blurple())
embed.set_author(
name="Extensions List",
url=Client.github_bot_repo,
diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py
index 4b766b50..db3c2aa6 100644
--- a/bot/exts/core/help.py
+++ b/bot/exts/core/help.py
@@ -13,10 +13,7 @@ from rapidfuzz import process
from bot import constants
from bot.bot import Bot
from bot.constants import Emojis
-from bot.utils.pagination import (
- FIRST_EMOJI, LAST_EMOJI,
- LEFT_EMOJI, LinePaginator, RIGHT_EMOJI,
-)
+from bot.utils.pagination import FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI
DELETE_EMOJI = Emojis.trashcan
diff --git a/bot/exts/core/internal_eval/_internal_eval.py b/bot/exts/core/internal_eval/_internal_eval.py
index 4f6b4321..190a15ec 100644
--- a/bot/exts/core/internal_eval/_internal_eval.py
+++ b/bot/exts/core/internal_eval/_internal_eval.py
@@ -10,6 +10,7 @@ from bot.bot import Bot
from bot.constants import Client, Roles
from bot.utils.decorators import with_role
from bot.utils.extensions import invoke_help_command
+
from ._helpers import EvalContext
__all__ = ["InternalEval"]
@@ -33,6 +34,8 @@ RAW_CODE_REGEX = re.compile(
re.DOTALL # "." also matches newlines
)
+MAX_LENGTH = 99980
+
class InternalEval(commands.Cog):
"""Top secret code evaluation for admins and owners."""
@@ -84,9 +87,10 @@ class InternalEval(commands.Cog):
async def _upload_output(self, output: str) -> Optional[str]:
"""Upload `internal eval` output to our pastebin and return the url."""
+ data = self.shorten_output(output, max_length=MAX_LENGTH)
try:
async with self.bot.http_session.post(
- "https://paste.pythondiscord.com/documents", data=output, raise_for_status=True
+ "https://paste.pythondiscord.com/documents", data=data, raise_for_status=True
) as resp:
data = await resp.json()
@@ -146,14 +150,14 @@ class InternalEval(commands.Cog):
await self._send_output(ctx, eval_context.format_output())
@commands.group(name="internal", aliases=("int",))
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
async def internal_group(self, ctx: commands.Context) -> None:
"""Internal commands. Top secret!"""
if not ctx.invoked_subcommand:
await invoke_help_command(ctx)
@internal_group.command(name="eval", aliases=("e",))
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
async def eval(self, ctx: commands.Context, *, code: str) -> None:
"""Run eval in a REPL-like format."""
if match := list(FORMATTED_CODE_REGEX.finditer(code)):
@@ -172,7 +176,7 @@ class InternalEval(commands.Cog):
await self._eval(ctx, code)
@internal_group.command(name="reset", aliases=("clear", "exit", "r", "c"))
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
async def reset(self, ctx: commands.Context) -> None:
"""Reset the context and locals of the eval session."""
self.locals = {}
diff --git a/bot/exts/core/source.py b/bot/exts/core/source.py
index 7572ce51..2801be0f 100644
--- a/bot/exts/core/source.py
+++ b/bot/exts/core/source.py
@@ -6,14 +6,16 @@ from discord import Embed
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import Source
+from bot.constants import Channels, Source, WHITELISTED_CHANNELS
from bot.utils.converters import SourceConverter, SourceType
+from bot.utils.decorators import whitelist_override
class BotSource(commands.Cog):
"""Displays information about the bot's source code."""
@commands.command(name="source", aliases=("src",))
+ @whitelist_override(channels=WHITELISTED_CHANNELS+(Channels.community_meta, Channels.dev_contrib))
async def source_command(self, ctx: commands.Context, *, source_item: SourceConverter = None) -> None:
"""Display information and a GitHub link to the source code of a command, tag, or cog."""
if not source_item:
diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py
index ca60e517..518841d4 100644
--- a/bot/exts/events/advent_of_code/_cog.py
+++ b/bot/exts/events/advent_of_code/_cog.py
@@ -2,17 +2,22 @@ import json
import logging
from datetime import datetime, timedelta
from pathlib import Path
+from typing import Optional
import arrow
import discord
-from discord.ext import commands
+from async_rediscache import RedisCache
+from discord.ext import commands, tasks
from bot.bot import Bot
from bot.constants import (
- AdventOfCode as AocConfig, Channels, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS,
+ AdventOfCode as AocConfig, Channels, Client, Colours, Emojis, Month, PYTHON_PREFIX, Roles, WHITELISTED_CHANNELS
)
from bot.exts.events.advent_of_code import _helpers
+from bot.exts.events.advent_of_code.views.dayandstarview import AoCDropdownView
+from bot.utils import members
from bot.utils.decorators import InChannelCheckFailure, in_month, whitelist_override, with_role
+from bot.utils.exceptions import MovedCommandError
from bot.utils.extensions import invoke_help_command
log = logging.getLogger(__name__)
@@ -29,6 +34,14 @@ AOC_WHITELIST = AOC_WHITELIST_RESTRICTED + (Channels.advent_of_code,)
class AdventOfCode(commands.Cog):
"""Advent of Code festivities! Ho Ho Ho!"""
+ # Redis Cache for linking Discord IDs to Advent of Code usernames
+ # RedisCache[member_id: aoc_username_string]
+ account_links = RedisCache()
+
+ # A dict with keys of member_ids to block from getting the role
+ # RedisCache[member_id: None]
+ completionist_block_list = RedisCache()
+
def __init__(self, bot: Bot):
self.bot = bot
@@ -48,6 +61,62 @@ class AdventOfCode(commands.Cog):
self.status_task.set_name("AoC Status Countdown")
self.status_task.add_done_callback(_helpers.background_task_callback)
+ # Don't start task while event isn't running
+ # self.completionist_task.start()
+
+ @tasks.loop(minutes=10.0)
+ async def completionist_task(self) -> None:
+ """
+ Give members who have completed all 50 AoC stars the completionist role.
+
+ Runs on a schedule, as defined in the task.loop decorator.
+ """
+ await self.bot.wait_until_guild_available()
+ guild = self.bot.get_guild(Client.guild)
+ completionist_role = guild.get_role(Roles.aoc_completionist)
+ if completionist_role is None:
+ log.warning("Could not find the AoC completionist role; cancelling completionist task.")
+ self.completionist_task.cancel()
+ return
+
+ aoc_name_to_member_id = {
+ aoc_name: member_id
+ for member_id, aoc_name in await self.account_links.items()
+ }
+
+ try:
+ leaderboard = await _helpers.fetch_leaderboard()
+ except _helpers.FetchingLeaderboardFailedError:
+ await self.bot.send_log("Unable to fetch AoC leaderboard during role sync.")
+ return
+
+ placement_leaderboard = json.loads(leaderboard["placement_leaderboard"])
+
+ for member_aoc_info in placement_leaderboard.values():
+ if not member_aoc_info["stars"] == 50:
+ # Only give the role to people who have completed all 50 stars
+ continue
+
+ aoc_name = member_aoc_info["name"] or f"Anonymous #{member_aoc_info['id']}"
+
+ member_id = aoc_name_to_member_id.get(aoc_name)
+ if not member_id:
+ log.debug(f"Could not find member_id for {member_aoc_info['name']}, not giving role.")
+ continue
+
+ member = await members.get_or_fetch_member(guild, member_id)
+ if member is None:
+ log.debug(f"Could not find {member_id}, not giving role.")
+ continue
+
+ if completionist_role in member.roles:
+ log.debug(f"{member.name} ({member.mention}) already has the completionist role.")
+ continue
+
+ if not await self.completionist_block_list.contains(member_id):
+ log.debug(f"Giving completionist role to {member.name} ({member.mention}).")
+ await members.handle_role_change(member, member.add_roles, completionist_role)
+
@commands.group(name="adventofcode", aliases=("aoc",))
@whitelist_override(channels=AOC_WHITELIST)
async def adventofcode_group(self, ctx: commands.Context) -> None:
@@ -55,77 +124,59 @@ class AdventOfCode(commands.Cog):
if not ctx.invoked_subcommand:
await invoke_help_command(ctx)
+ @with_role(Roles.admins)
@adventofcode_group.command(
- name="subscribe",
- aliases=("sub", "notifications", "notify", "notifs"),
- brief="Notifications for new days"
+ name="block",
+ brief="Block a user from getting the completionist role.",
)
- @whitelist_override(channels=AOC_WHITELIST)
- async def aoc_subscribe(self, ctx: commands.Context) -> None:
- """Assign the role for notifications about new days being ready."""
- current_year = datetime.now().year
- if current_year != AocConfig.year:
- await ctx.send(f"You can't subscribe to {current_year}'s Advent of Code announcements yet!")
- return
+ async def block_from_role(self, ctx: commands.Context, member: discord.Member) -> None:
+ """Block the given member from receiving the AoC completionist role, removing it from them if needed."""
+ completionist_role = ctx.guild.get_role(Roles.aoc_completionist)
+ if completionist_role in member.roles:
+ await member.remove_roles(completionist_role)
- role = ctx.guild.get_role(AocConfig.role_id)
- unsubscribe_command = f"{ctx.prefix}{ctx.command.root_parent} unsubscribe"
+ await self.completionist_block_list.set(member.id, "sentinel")
+ await ctx.send(f":+1: Blocked {member.mention} from getting the AoC completionist role.")
- if role not in ctx.author.roles:
- await ctx.author.add_roles(role)
- await ctx.send(
- "Okay! You have been __subscribed__ to notifications about new Advent of Code tasks. "
- f"You can run `{unsubscribe_command}` to disable them again for you."
- )
- else:
- await ctx.send(
- "Hey, you already are receiving notifications about new Advent of Code tasks. "
- f"If you don't want them any more, run `{unsubscribe_command}` instead."
- )
-
- @in_month(Month.DECEMBER)
- @adventofcode_group.command(name="unsubscribe", aliases=("unsub",), brief="Notifications for new days")
+ @commands.guild_only()
+ @adventofcode_group.command(
+ name="subscribe",
+ aliases=("sub", "notifications", "notify", "notifs", "unsubscribe", "unsub"),
+ help=f"NOTE: This command has been moved to {PYTHON_PREFIX}subscribe",
+ )
@whitelist_override(channels=AOC_WHITELIST)
- async def aoc_unsubscribe(self, ctx: commands.Context) -> None:
- """Remove the role for notifications about new days being ready."""
- role = ctx.guild.get_role(AocConfig.role_id)
+ async def aoc_subscribe(self, ctx: commands.Context) -> None:
+ """
+ Deprecated role command.
- if role in ctx.author.roles:
- await ctx.author.remove_roles(role)
- await ctx.send("Okay! You have been __unsubscribed__ from notifications about new Advent of Code tasks.")
- else:
- await ctx.send("Hey, you don't even get any notifications about new Advent of Code tasks currently anyway.")
+ This command has been moved to bot, and will be removed in the future.
+ """
+ raise MovedCommandError(f"{PYTHON_PREFIX}subscribe")
@adventofcode_group.command(name="countdown", aliases=("count", "c"), brief="Return time left until next day")
@whitelist_override(channels=AOC_WHITELIST)
async def aoc_countdown(self, ctx: commands.Context) -> None:
"""Return time left until next day."""
- if not _helpers.is_in_advent():
- datetime_now = arrow.now(_helpers.EST)
-
- # Calculate the delta to this & next year's December 1st to see which one is closest and not in the past
- this_year = arrow.get(datetime(datetime_now.year, 12, 1), _helpers.EST)
- next_year = arrow.get(datetime(datetime_now.year + 1, 12, 1), _helpers.EST)
- deltas = (dec_first - datetime_now for dec_first in (this_year, next_year))
- delta = min(delta for delta in deltas if delta >= timedelta()) # timedelta() gives 0 duration delta
-
- # Add a finer timedelta if there's less than a day left
- if delta.days == 0:
- delta_str = f"approximately {delta.seconds // 3600} hours"
- else:
- delta_str = f"{delta.days} days"
+ if _helpers.is_in_advent():
+ tomorrow, _ = _helpers.time_left_to_est_midnight()
+ next_day_timestamp = int(tomorrow.timestamp())
- await ctx.send(
- "The Advent of Code event is not currently running. "
- f"The next event will start in {delta_str}."
- )
+ await ctx.send(f"Day {tomorrow.day} starts <t:{next_day_timestamp}:R>.")
return
- tomorrow, time_left = _helpers.time_left_to_est_midnight()
+ datetime_now = arrow.now(_helpers.EST)
+ # Calculate the delta to this & next year's December 1st to see which one is closest and not in the past
+ this_year = arrow.get(datetime(datetime_now.year, 12, 1), _helpers.EST)
+ next_year = arrow.get(datetime(datetime_now.year + 1, 12, 1), _helpers.EST)
+ deltas = (dec_first - datetime_now for dec_first in (this_year, next_year))
+ delta = min(delta for delta in deltas if delta >= timedelta()) # timedelta() gives 0 duration delta
- hours, minutes = time_left.seconds // 3600, time_left.seconds // 60 % 60
+ next_aoc_timestamp = int((datetime_now + delta).timestamp())
- await ctx.send(f"There are {hours} hours and {minutes} minutes left until day {tomorrow.day}.")
+ await ctx.send(
+ "The Advent of Code event is not currently running. "
+ f"The next event will start <t:{next_aoc_timestamp}:R>."
+ )
@adventofcode_group.command(name="about", aliases=("ab", "info"), brief="Learn about Advent of Code")
@whitelist_override(channels=AOC_WHITELIST)
@@ -133,13 +184,19 @@ class AdventOfCode(commands.Cog):
"""Respond with an explanation of all things Advent of Code."""
await ctx.send(embed=self.cached_about_aoc)
+ @commands.guild_only()
@adventofcode_group.command(name="join", aliases=("j",), brief="Learn how to join the leaderboard (via DM)")
@whitelist_override(channels=AOC_WHITELIST)
async def join_leaderboard(self, ctx: commands.Context) -> None:
"""DM the user the information for joining the Python Discord leaderboard."""
- current_year = datetime.now().year
- if current_year != AocConfig.year:
- await ctx.send(f"The Python Discord leaderboard for {current_year} is not yet available!")
+ current_date = datetime.now()
+ allowed_months = (Month.NOVEMBER.value, Month.DECEMBER.value)
+ if not (
+ current_date.month in allowed_months and current_date.year == AocConfig.year or
+ current_date.month == Month.JANUARY.value and current_date.year == AocConfig.year + 1
+ ):
+ # Only allow joining the leaderboard in the run up to AOC and the January following.
+ await ctx.send(f"The Python Discord leaderboard for {current_date.year} is not yet available!")
return
author = ctx.author
@@ -150,7 +207,7 @@ class AdventOfCode(commands.Cog):
else:
try:
join_code = await _helpers.get_public_join_code(author)
- except _helpers.FetchingLeaderboardFailed:
+ except _helpers.FetchingLeaderboardFailedError:
await ctx.send(":x: Failed to get join code! Notified maintainers.")
return
@@ -178,33 +235,163 @@ class AdventOfCode(commands.Cog):
else:
await ctx.message.add_reaction(Emojis.envelope)
- @in_month(Month.DECEMBER)
+ @in_month(Month.NOVEMBER, Month.DECEMBER, Month.JANUARY)
+ @adventofcode_group.command(
+ name="link",
+ aliases=("connect",),
+ brief="Tie your Discord account with your Advent of Code name."
+ )
+ @whitelist_override(channels=AOC_WHITELIST)
+ async def aoc_link_account(self, ctx: commands.Context, *, aoc_name: str = None) -> None:
+ """
+ Link your Discord Account to your Advent of Code name.
+
+ Stored in a Redis Cache with the format of `Discord ID: Advent of Code Name`
+ """
+ cache_items = await self.account_links.items()
+ cache_aoc_names = [value for _, value in cache_items]
+
+ if aoc_name:
+ # Let's check the current values in the cache to make sure it isn't already tied to a different account
+ if aoc_name == await self.account_links.get(ctx.author.id):
+ await ctx.reply(f"{aoc_name} is already tied to your account.")
+ return
+ elif aoc_name in cache_aoc_names:
+ log.info(
+ f"{ctx.author} ({ctx.author.id}) tried to connect their account to {aoc_name},"
+ " but it's already connected to another user."
+ )
+ await ctx.reply(
+ f"{aoc_name} is already tied to another account."
+ " Please contact an admin if you believe this is an error."
+ )
+ return
+
+ # Update an existing link
+ if old_aoc_name := await self.account_links.get(ctx.author.id):
+ log.info(f"Changing link for {ctx.author} ({ctx.author.id}) from {old_aoc_name} to {aoc_name}.")
+ await self.account_links.set(ctx.author.id, aoc_name)
+ await ctx.reply(f"Your linked account has been changed to {aoc_name}.")
+ else:
+ # Create a new link
+ log.info(f"Linking {ctx.author} ({ctx.author.id}) to account {aoc_name}.")
+ await self.account_links.set(ctx.author.id, aoc_name)
+ await ctx.reply(f"You have linked your Discord ID to {aoc_name}.")
+ else:
+ # User has not supplied a name, let's check if they're in the cache or not
+ if cache_name := await self.account_links.get(ctx.author.id):
+ await ctx.reply(f"You have already linked an Advent of Code account: {cache_name}.")
+ else:
+ await ctx.reply(
+ "You have not linked an Advent of Code account."
+ " Please re-run the command with one specified."
+ )
+
+ @in_month(Month.NOVEMBER, Month.DECEMBER, Month.JANUARY)
+ @adventofcode_group.command(
+ name="unlink",
+ aliases=("disconnect",),
+ brief="Tie your Discord account with your Advent of Code name."
+ )
+ @whitelist_override(channels=AOC_WHITELIST)
+ async def aoc_unlink_account(self, ctx: commands.Context) -> None:
+ """
+ Unlink your Discord ID with your Advent of Code leaderboard name.
+
+ Deletes the entry that was Stored in the Redis cache.
+ """
+ if aoc_cache_name := await self.account_links.get(ctx.author.id):
+ log.info(f"Unlinking {ctx.author} ({ctx.author.id}) from Advent of Code account {aoc_cache_name}")
+ await self.account_links.delete(ctx.author.id)
+ await ctx.reply(f"We have removed the link between your Discord ID and {aoc_cache_name}.")
+ else:
+ log.info(f"Attempted to unlink {ctx.author} ({ctx.author.id}), but no link was found.")
+ await ctx.reply("You don't have an Advent of Code account linked.")
+
+ @in_month(Month.DECEMBER, Month.JANUARY)
+ @adventofcode_group.command(
+ name="dayandstar",
+ aliases=("daynstar", "daystar"),
+ brief="Get a view that lets you filter the leaderboard by day and star",
+ )
+ @whitelist_override(channels=AOC_WHITELIST_RESTRICTED)
+ async def aoc_day_and_star_leaderboard(
+ self,
+ ctx: commands.Context,
+ maximum_scorers_day_and_star: Optional[int] = 10
+ ) -> None:
+ """Have the bot send a View that will let you filter the leaderboard by day and star."""
+ if maximum_scorers_day_and_star > AocConfig.max_day_and_star_results or maximum_scorers_day_and_star <= 0:
+ raise commands.BadArgument(
+ f"The maximum number of results you can query is {AocConfig.max_day_and_star_results}"
+ )
+ async with ctx.typing():
+ try:
+ leaderboard = await _helpers.fetch_leaderboard()
+ except _helpers.FetchingLeaderboardFailedError:
+ await ctx.send(":x: Unable to fetch leaderboard!")
+ return
+ # This is a dictionary that contains solvers in respect of day, and star.
+ # e.g. 1-1 means the solvers of the first star of the first day and their completion time
+ per_day_and_star = json.loads(leaderboard['leaderboard_per_day_and_star'])
+ view = AoCDropdownView(
+ day_and_star_data=per_day_and_star,
+ maximum_scorers=maximum_scorers_day_and_star,
+ original_author=ctx.author
+ )
+ message = await ctx.send(
+ content="Please select a day and a star to filter by!",
+ view=view
+ )
+ await view.wait()
+ await message.edit(view=None)
+
+ @in_month(Month.DECEMBER, Month.JANUARY)
@adventofcode_group.command(
name="leaderboard",
aliases=("board", "lb"),
brief="Get a snapshot of the PyDis private AoC leaderboard",
)
@whitelist_override(channels=AOC_WHITELIST_RESTRICTED)
- async def aoc_leaderboard(self, ctx: commands.Context) -> None:
- """Get the current top scorers of the Python Discord Leaderboard."""
+ async def aoc_leaderboard(self, ctx: commands.Context, *, aoc_name: Optional[str] = None) -> None:
+ """
+ Get the current top scorers of the Python Discord Leaderboard.
+
+ Additionally you can specify an `aoc_name` that will append the
+ specified profile's personal stats to the top of the leaderboard
+ """
+ # Strip quotes from the AoC username if needed (e.g. "My Name" -> My Name)
+ # This is to keep compatibility with those already used to wrapping the AoC name in quotes
+ # Note: only strips one layer of quotes to allow names with quotes at the start and end
+ # e.g. ""My Name"" -> "My Name"
+ if aoc_name and aoc_name.startswith('"') and aoc_name.endswith('"'):
+ aoc_name = aoc_name[1:-1]
+
+ # Check if an advent of code account is linked in the Redis Cache if aoc_name is not given
+ if (aoc_cache_name := await self.account_links.get(ctx.author.id)) and aoc_name is None:
+ aoc_name = aoc_cache_name
+
async with ctx.typing():
try:
- leaderboard = await _helpers.fetch_leaderboard()
- except _helpers.FetchingLeaderboardFailed:
+ leaderboard = await _helpers.fetch_leaderboard(self_placement_name=aoc_name)
+ except _helpers.FetchingLeaderboardFailedError:
await ctx.send(":x: Unable to fetch leaderboard!")
return
- number_of_participants = leaderboard["number_of_participants"]
+ number_of_participants = leaderboard["number_of_participants"]
- top_count = min(AocConfig.leaderboard_displayed_members, number_of_participants)
- header = f"Here's our current top {top_count}! {Emojis.christmas_tree * 3}"
-
- table = f"```\n{leaderboard['top_leaderboard']}\n```"
- info_embed = _helpers.get_summary_embed(leaderboard)
+ top_count = min(AocConfig.leaderboard_displayed_members, number_of_participants)
+ self_placement_header = " (and your personal stats compared to the top 10)" if aoc_name else ""
+ header = f"Here's our current top {top_count}{self_placement_header}! {Emojis.christmas_tree * 3}"
+ table = "```\n" \
+ f"{leaderboard['placement_leaderboard'] if aoc_name else leaderboard['top_leaderboard']}" \
+ "\n```"
+ info_embed = _helpers.get_summary_embed(leaderboard)
- await ctx.send(content=f"{header}\n\n{table}", embed=info_embed)
+ await ctx.send(content=f"{header}\n\n{table}", embed=info_embed)
+ return
- @in_month(Month.DECEMBER)
+ @in_month(Month.DECEMBER, Month.JANUARY)
@adventofcode_group.command(
name="global",
aliases=("globalboard", "gb"),
@@ -231,7 +418,7 @@ class AdventOfCode(commands.Cog):
"""Send an embed with daily completion statistics for the Python Discord leaderboard."""
try:
leaderboard = await _helpers.fetch_leaderboard()
- except _helpers.FetchingLeaderboardFailed:
+ except _helpers.FetchingLeaderboardFailedError:
await ctx.send(":x: Can't fetch leaderboard for stats right now!")
return
@@ -251,7 +438,7 @@ class AdventOfCode(commands.Cog):
info_embed = _helpers.get_summary_embed(leaderboard)
await ctx.send(f"```\n{table}\n```", embed=info_embed)
- @with_role(Roles.admin)
+ @with_role(Roles.admins)
@adventofcode_group.command(
name="refresh",
aliases=("fetch",),
@@ -267,7 +454,7 @@ class AdventOfCode(commands.Cog):
async with ctx.typing():
try:
await _helpers.fetch_leaderboard(invalidate_cache=True)
- except _helpers.FetchingLeaderboardFailed:
+ except _helpers.FetchingLeaderboardFailedError:
await ctx.send(":x: Something went wrong while trying to refresh the cache!")
else:
await ctx.send("\N{OK Hand Sign} Refreshed leaderboard cache!")
@@ -277,6 +464,7 @@ class AdventOfCode(commands.Cog):
log.debug("Unloading the cog and canceling the background task.")
self.notification_task.cancel()
self.status_task.cancel()
+ self.completionist_task.cancel()
def _build_about_embed(self) -> discord.Embed:
"""Build and return the informational "About AoC" embed from the resources file."""
diff --git a/bot/exts/events/advent_of_code/_helpers.py b/bot/exts/events/advent_of_code/_helpers.py
index 5fedb60f..6c004901 100644
--- a/bot/exts/events/advent_of_code/_helpers.py
+++ b/bot/exts/events/advent_of_code/_helpers.py
@@ -10,6 +10,7 @@ from typing import Any, Optional
import aiohttp
import arrow
import discord
+from discord.ext import commands
from bot.bot import Bot
from bot.constants import AdventOfCode, Channels, Colours
@@ -70,6 +71,33 @@ class FetchingLeaderboardFailedError(Exception):
"""Raised when one or more leaderboards could not be fetched at all."""
+def _format_leaderboard_line(rank: int, data: dict[str, Any], *, is_author: bool) -> str:
+ """
+ Build a string representing a line of the leaderboard.
+
+ Parameters:
+ rank:
+ Rank in the leaderboard of this entry.
+
+ data:
+ Mapping with entry information.
+
+ Keyword arguments:
+ is_author:
+ Whether to address the name displayed in the returned line
+ personally.
+
+ Returns:
+ A formatted line for the leaderboard.
+ """
+ return AOC_TABLE_TEMPLATE.format(
+ rank=rank,
+ name=data['name'] if not is_author else f"(You) {data['name']}",
+ score=str(data['score']),
+ stars=f"({data['star_1']}, {data['star_2']})"
+ )
+
+
def leaderboard_sorting_function(entry: tuple[str, dict]) -> tuple[int, int]:
"""
Provide a sorting value for our leaderboard.
@@ -105,6 +133,7 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict:
# The data we get from the AoC website is structured by member, not by day/star,
# which means we need to iterate over the members to transpose the data to a per
# star view. We need that per star view to compute rank scores per star.
+ per_day_star_stats = collections.defaultdict(list)
for member in raw_leaderboard_data.values():
name = member["name"] if member["name"] else f"Anonymous #{member['id']}"
member_id = member["id"]
@@ -122,6 +151,11 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict:
star_results[(day, star)].append(
StarResult(member_id=member_id, completion_time=completion_time)
)
+ per_day_star_stats[f"{day}-{star}"].append(
+ {'completion_time': int(data["get_star_ts"]), 'member_name': name}
+ )
+ for key in per_day_star_stats:
+ per_day_star_stats[key] = sorted(per_day_star_stats[key], key=operator.itemgetter('completion_time'))
# Now that we have a transposed dataset that holds the completion time of all
# participants per star, we can compute the rank-based scores each participant
@@ -151,13 +185,26 @@ def _parse_raw_leaderboard_data(raw_leaderboard_data: dict) -> dict:
# this data to JSON in order to cache it in Redis.
daily_stats[day] = {"star_one": star_one, "star_two": star_two}
- return {"daily_stats": daily_stats, "leaderboard": sorted_leaderboard}
+ return {"daily_stats": daily_stats, "leaderboard": sorted_leaderboard, 'per_day_and_star': per_day_star_stats}
-def _format_leaderboard(leaderboard: dict[str, dict]) -> str:
+def _format_leaderboard(leaderboard: dict[str, dict], self_placement_name: str = None) -> str:
"""Format the leaderboard using the AOC_TABLE_TEMPLATE."""
leaderboard_lines = [HEADER]
+ self_placement_exists = False
for rank, data in enumerate(leaderboard.values(), start=1):
+ if self_placement_name and data["name"].lower() == self_placement_name.lower():
+ leaderboard_lines.insert(
+ 1,
+ AOC_TABLE_TEMPLATE.format(
+ rank=rank,
+ name=f"(You) {data['name']}",
+ score=str(data["score"]),
+ stars=f"({data['star_1']}, {data['star_2']})"
+ )
+ )
+ self_placement_exists = True
+ continue
leaderboard_lines.append(
AOC_TABLE_TEMPLATE.format(
rank=rank,
@@ -166,7 +213,13 @@ def _format_leaderboard(leaderboard: dict[str, dict]) -> str:
stars=f"({data['star_1']}, {data['star_2']})"
)
)
-
+ if self_placement_name and not self_placement_exists:
+ raise commands.BadArgument(
+ "Sorry, your profile does not exist in this leaderboard."
+ "\n\n"
+ "To join our leaderboard, run the command `.aoc join`."
+ " If you've joined recently, please wait up to 30 minutes for our leaderboard to refresh."
+ )
return "\n".join(leaderboard_lines)
@@ -202,7 +255,7 @@ async def _fetch_leaderboard_data() -> dict[str, Any]:
# Two attempts, one with the original session cookie and one with the fallback session
for attempt in range(1, 3):
- log.info(f"Attempting to fetch leaderboard `{leaderboard.id}` ({attempt}/2)")
+ log.debug(f"Attempting to fetch leaderboard `{leaderboard.id}` ({attempt}/2)")
cookies = {"session": leaderboard.session}
try:
raw_data = await _leaderboard_request(leaderboard_url, leaderboard.id, cookies)
@@ -254,7 +307,7 @@ def _get_top_leaderboard(full_leaderboard: str) -> str:
@_caches.leaderboard_cache.atomic_transaction
-async def fetch_leaderboard(invalidate_cache: bool = False) -> dict:
+async def fetch_leaderboard(invalidate_cache: bool = False, self_placement_name: str = None) -> dict:
"""
Get the current Python Discord combined leaderboard.
@@ -264,7 +317,6 @@ async def fetch_leaderboard(invalidate_cache: bool = False) -> dict:
miss, this function is locked to one call at a time using a decorator.
"""
cached_leaderboard = await _caches.leaderboard_cache.to_dict()
-
# Check if the cached leaderboard contains everything we expect it to. If it
# does not, this probably means the cache has not been created yet or has
# expired in Redis. This check also accounts for a malformed cache.
@@ -280,15 +332,17 @@ async def fetch_leaderboard(invalidate_cache: bool = False) -> dict:
number_of_participants = len(leaderboard)
formatted_leaderboard = _format_leaderboard(leaderboard)
full_leaderboard_url = await _upload_leaderboard(formatted_leaderboard)
- leaderboard_fetched_at = datetime.datetime.utcnow().isoformat()
+ leaderboard_fetched_at = datetime.datetime.now(datetime.timezone.utc).isoformat()
cached_leaderboard = {
+ "placement_leaderboard": json.dumps(raw_leaderboard_data),
"full_leaderboard": formatted_leaderboard,
"top_leaderboard": _get_top_leaderboard(formatted_leaderboard),
"full_leaderboard_url": full_leaderboard_url,
"leaderboard_fetched_at": leaderboard_fetched_at,
"number_of_participants": number_of_participants,
"daily_stats": json.dumps(parsed_leaderboard_data["daily_stats"]),
+ "leaderboard_per_day_and_star": json.dumps(parsed_leaderboard_data["per_day_and_star"])
}
# Store the new values in Redis
@@ -300,7 +354,13 @@ async def fetch_leaderboard(invalidate_cache: bool = False) -> dict:
_caches.leaderboard_cache.namespace,
AdventOfCode.leaderboard_cache_expiry_seconds
)
-
+ if self_placement_name:
+ formatted_placement_leaderboard = _parse_raw_leaderboard_data(
+ json.loads(cached_leaderboard["placement_leaderboard"])
+ )["leaderboard"]
+ cached_leaderboard["placement_leaderboard"] = _get_top_leaderboard(
+ _format_leaderboard(formatted_placement_leaderboard, self_placement_name=self_placement_name)
+ )
return cached_leaderboard
@@ -308,11 +368,13 @@ def get_summary_embed(leaderboard: dict) -> discord.Embed:
"""Get an embed with the current summary stats of the leaderboard."""
leaderboard_url = leaderboard["full_leaderboard_url"]
refresh_minutes = AdventOfCode.leaderboard_cache_expiry_seconds // 60
+ refreshed_unix = int(datetime.datetime.fromisoformat(leaderboard["leaderboard_fetched_at"]).timestamp())
+
+ aoc_embed = discord.Embed(colour=Colours.soft_green)
- aoc_embed = discord.Embed(
- colour=Colours.soft_green,
- timestamp=datetime.datetime.fromisoformat(leaderboard["leaderboard_fetched_at"]),
- description=f"*The leaderboard is refreshed every {refresh_minutes} minutes.*"
+ aoc_embed.description = (
+ f"The leaderboard is refreshed every {refresh_minutes} minutes.\n"
+ f"Last Updated: <t:{refreshed_unix}:t>"
)
aoc_embed.add_field(
name="Number of Participants",
@@ -326,7 +388,6 @@ def get_summary_embed(leaderboard: dict) -> discord.Embed:
inline=True,
)
aoc_embed.set_author(name="Advent of Code", url=leaderboard_url)
- aoc_embed.set_footer(text="Last Updated")
aoc_embed.set_thumbnail(url=AOC_EMBED_THUMBNAIL)
return aoc_embed
diff --git a/bot/exts/events/advent_of_code/views/dayandstarview.py b/bot/exts/events/advent_of_code/views/dayandstarview.py
new file mode 100644
index 00000000..5529c12b
--- /dev/null
+++ b/bot/exts/events/advent_of_code/views/dayandstarview.py
@@ -0,0 +1,82 @@
+from datetime import datetime
+
+import discord
+
+AOC_DAY_AND_STAR_TEMPLATE = "{rank: >4} | {name:25.25} | {completion_time: >10}"
+
+
+class AoCDropdownView(discord.ui.View):
+ """Interactive view to filter AoC stats by Day and Star."""
+
+ def __init__(self, original_author: discord.Member, day_and_star_data: dict[str: dict], maximum_scorers: int):
+ super().__init__()
+ self.day = 0
+ self.star = 0
+ self.data = day_and_star_data
+ self.maximum_scorers = maximum_scorers
+ self.original_author = original_author
+
+ def generate_output(self) -> str:
+ """
+ Generates a formatted codeblock with AoC statistics based on the currently selected day and star.
+
+ Optionally, when the requested day and star data does not exist yet it returns an error message.
+ """
+ header = AOC_DAY_AND_STAR_TEMPLATE.format(
+ rank="Rank",
+ name="Name", completion_time="Completion time (UTC)"
+ )
+ lines = [f"{header}\n{'-' * (len(header) + 2)}"]
+ if not (day_and_star_data := self.data.get(f"{self.day}-{self.star}")):
+ return ":x: The requested data for the specified day and star does not exist yet."
+ for rank, scorer in enumerate(day_and_star_data[:self.maximum_scorers]):
+ time_data = datetime.fromtimestamp(scorer['completion_time']).strftime("%I:%M:%S %p")
+ lines.append(AOC_DAY_AND_STAR_TEMPLATE.format(
+ datastamp="",
+ rank=rank + 1,
+ name=scorer['member_name'],
+ completion_time=time_data)
+ )
+ joined_lines = "\n".join(lines)
+ return f"Statistics for Day: {self.day}, Star: {self.star}.\n ```\n{joined_lines}\n```"
+
+ async def interaction_check(self, interaction: discord.Interaction) -> bool:
+ """Global check to ensure that the interacting user is the user who invoked the command originally."""
+ if interaction.user != self.original_author:
+ await interaction.response.send_message(
+ ":x: You can't interact with someone else's response. Please run the command yourself!",
+ ephemeral=True
+ )
+ return False
+ return True
+
+ @discord.ui.select(
+ placeholder="Day",
+ options=[discord.SelectOption(label=str(i)) for i in range(1, 26)],
+ custom_id="day_select"
+ )
+ async def day_select(self, select: discord.ui.Select, interaction: discord.Interaction) -> None:
+ """Dropdown to choose a Day of the AoC."""
+ self.day = select.values[0]
+
+ @discord.ui.select(
+ placeholder="Star",
+ options=[discord.SelectOption(label=str(i)) for i in range(1, 3)],
+ custom_id="star_select"
+ )
+ async def star_select(self, select: discord.ui.Select, interaction: discord.Interaction) -> None:
+ """Dropdown to choose either the first or the second star."""
+ self.star = select.values[0]
+
+ @discord.ui.button(label="Fetch", style=discord.ButtonStyle.blurple)
+ async def fetch(self, button: discord.ui.Button, interaction: discord.Interaction) -> None:
+ """Button that fetches the statistics based on the dropdown values."""
+ if self.day == 0 or self.star == 0:
+ await interaction.response.send_message(
+ "You have to select a value from both of the dropdowns!",
+ ephemeral=True
+ )
+ else:
+ await interaction.response.edit_message(content=self.generate_output())
+ self.day = 0
+ self.star = 0
diff --git a/bot/exts/events/hacktoberfest/hacktober-issue-finder.py b/bot/exts/events/hacktoberfest/hacktober-issue-finder.py
index e3053851..1774564b 100644
--- a/bot/exts/events/hacktoberfest/hacktober-issue-finder.py
+++ b/bot/exts/events/hacktoberfest/hacktober-issue-finder.py
@@ -52,10 +52,10 @@ class HacktoberIssues(commands.Cog):
async def get_issues(self, ctx: commands.Context, option: str) -> Optional[dict]:
"""Get a list of the python issues with the label 'hacktoberfest' from the Github api."""
if option == "beginner":
- if (ctx.message.created_at - self.cache_timer_beginner).seconds <= 60:
+ if (ctx.message.created_at.replace(tzinfo=None) - self.cache_timer_beginner).seconds <= 60:
log.debug("using cache")
return self.cache_beginner
- elif (ctx.message.created_at - self.cache_timer_normal).seconds <= 60:
+ elif (ctx.message.created_at.replace(tzinfo=None) - self.cache_timer_normal).seconds <= 60:
log.debug("using cache")
return self.cache_normal
@@ -88,10 +88,10 @@ class HacktoberIssues(commands.Cog):
if option == "beginner":
self.cache_beginner = data
- self.cache_timer_beginner = ctx.message.created_at
+ self.cache_timer_beginner = ctx.message.created_at.replace(tzinfo=None)
else:
self.cache_normal = data
- self.cache_timer_normal = ctx.message.created_at
+ self.cache_timer_normal = ctx.message.created_at.replace(tzinfo=None)
return data
@@ -100,7 +100,8 @@ class HacktoberIssues(commands.Cog):
"""Format the issue data into a embed."""
title = issue["title"]
issue_url = issue["url"].replace("api.", "").replace("/repos/", "/")
- body = issue["body"]
+ # issues can have empty bodies, which in that case GitHub doesn't include the key in the API response
+ body = issue.get("body", "")
labels = [label["name"] for label in issue["labels"]]
embed = discord.Embed(title=title)
diff --git a/bot/exts/events/trivianight/__init__.py b/bot/exts/events/trivianight/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/bot/exts/events/trivianight/__init__.py
diff --git a/bot/exts/events/trivianight/_game.py b/bot/exts/events/trivianight/_game.py
new file mode 100644
index 00000000..8b012a17
--- /dev/null
+++ b/bot/exts/events/trivianight/_game.py
@@ -0,0 +1,192 @@
+import time
+from random import randrange
+from string import ascii_uppercase
+from typing import Iterable, NamedTuple, Optional, TypedDict
+
+DEFAULT_QUESTION_POINTS = 10
+DEFAULT_QUESTION_TIME = 20
+
+
+class QuestionData(TypedDict):
+ """Representing the different 'keys' of the question taken from the JSON."""
+
+ number: str
+ description: str
+ answers: list[str]
+ correct: str
+ points: Optional[int]
+ time: Optional[int]
+
+
+class UserGuess(NamedTuple):
+ """Represents the user's guess for a question."""
+
+ answer: str
+ editable: bool
+ elapsed: float
+
+
+class QuestionClosed(RuntimeError):
+ """Exception raised when the question is not open for guesses anymore."""
+
+
+class AlreadyUpdated(RuntimeError):
+ """Exception raised when the user has already updated their guess once."""
+
+
+class AllQuestionsVisited(RuntimeError):
+ """Exception raised when all of the questions have been visited."""
+
+
+class Question:
+ """Interface for one question in a trivia night game."""
+
+ def __init__(self, data: QuestionData):
+ self._data = data
+ self._guesses: dict[int, UserGuess] = {}
+ self._started = None
+
+ # These properties are mostly proxies to the underlying data:
+
+ @property
+ def number(self) -> str:
+ """The number of the question."""
+ return self._data["number"]
+
+ @property
+ def description(self) -> str:
+ """The description of the question."""
+ return self._data["description"]
+
+ @property
+ def answers(self) -> list[tuple[str, str]]:
+ """
+ The possible answers for this answer.
+
+ This is a property that returns a list of letter, answer pairs.
+ """
+ return [(ascii_uppercase[i], q) for (i, q) in enumerate(self._data["answers"])]
+
+ @property
+ def correct(self) -> str:
+ """The correct answer for this question."""
+ return self._data["correct"]
+
+ @property
+ def max_points(self) -> int:
+ """The maximum points that can be awarded for this question."""
+ return self._data.get("points") or DEFAULT_QUESTION_POINTS
+
+ @property
+ def time(self) -> float:
+ """The time allowed to answer the question."""
+ return self._data.get("time") or DEFAULT_QUESTION_TIME
+
+ def start(self) -> float:
+ """Start the question and return the time it started."""
+ self._started = time.perf_counter()
+ return self._started
+
+ def _update_guess(self, user: int, answer: str) -> UserGuess:
+ """Update an already existing guess."""
+ if self._started is None:
+ raise QuestionClosed("Question is not open for answers.")
+
+ if self._guesses[user][1] is False:
+ raise AlreadyUpdated(f"User({user}) has already updated their guess once.")
+
+ self._guesses[user] = (answer, False, time.perf_counter() - self._started)
+ return self._guesses[user]
+
+ def guess(self, user: int, answer: str) -> UserGuess:
+ """Add a guess made by a user to the current question."""
+ if user in self._guesses:
+ return self._update_guess(user, answer)
+
+ if self._started is None:
+ raise QuestionClosed("Question is not open for answers.")
+
+ self._guesses[user] = (answer, True, time.perf_counter() - self._started)
+ return self._guesses[user]
+
+ def stop(self) -> dict[int, UserGuess]:
+ """Stop the question and return the guesses that were made."""
+ guesses = self._guesses
+
+ self._started = None
+ self._guesses = {}
+
+ return guesses
+
+
+class TriviaNightGame:
+ """Interface for managing a game of trivia night."""
+
+ def __init__(self, data: list[QuestionData]) -> None:
+ self._questions = [Question(q) for q in data]
+ # A copy of the questions to keep for `.trivianight list`
+ self._all_questions = list(self._questions)
+ self.current_question: Optional[Question] = None
+ self._points = {}
+ self._speed = {}
+
+ def __iter__(self) -> Iterable[Question]:
+ return iter(self._questions)
+
+ def next_question(self, number: str = None) -> Question:
+ """
+ Consume one random question from the trivia night game.
+
+ One question is randomly picked from the list of questions which is then removed and returned.
+ """
+ if self.current_question is not None:
+ raise RuntimeError("Cannot call next_question() when there is a current question.")
+
+ if number is not None:
+ try:
+ question = [q for q in self._all_questions if q.number == int(number)][0]
+ except IndexError:
+ raise ValueError(f"Question number {number} does not exist.")
+ elif len(self._questions) == 0:
+ raise AllQuestionsVisited("All of the questions have been visited.")
+ else:
+ question = self._questions.pop(randrange(len(self._questions)))
+
+ self.current_question = question
+ return question
+
+ def end_question(self) -> None:
+ """
+ End the current question.
+
+ This method should be called when the question has been answered, it must be called before
+ attempting to call `next_question()` again.
+ """
+ if self.current_question is None:
+ raise RuntimeError("Cannot call end_question() when there is no current question.")
+
+ self.current_question.stop()
+ self.current_question = None
+
+ def list_questions(self) -> str:
+ """
+ List all the questions.
+
+ This method should be called when `.trivianight list` is called to display the following information:
+ - Question number
+ - Question description
+ - Visited/not visited
+ """
+ question_list = []
+
+ visited = ":white_check_mark:"
+ not_visited = ":x:"
+
+ for question in self._all_questions:
+ formatted_string = (
+ f"**Q{question.number}** {not_visited if question in self._questions else visited}"
+ f"\n{question.description}\n\n"
+ )
+ question_list.append(formatted_string.rstrip())
+
+ return question_list
diff --git a/bot/exts/events/trivianight/_questions.py b/bot/exts/events/trivianight/_questions.py
new file mode 100644
index 00000000..d6beced9
--- /dev/null
+++ b/bot/exts/events/trivianight/_questions.py
@@ -0,0 +1,179 @@
+from random import choice
+from string import ascii_uppercase
+
+import discord
+from discord import Embed, Interaction
+from discord.ui import Button, View
+
+from bot.constants import Colours, NEGATIVE_REPLIES
+
+from ._game import AlreadyUpdated, Question, QuestionClosed
+from ._scoreboard import Scoreboard
+
+
+class AnswerButton(Button):
+ """Button subclass that's used to guess on a particular answer."""
+
+ def __init__(self, label: str, question: Question):
+ super().__init__(label=label, style=discord.ButtonStyle.green)
+
+ self.question = question
+
+ async def callback(self, interaction: Interaction) -> None:
+ """
+ When a user interacts with the button, this will be called.
+
+ Parameters:
+ - interaction: an instance of discord.Interaction representing the interaction between the user and the
+ button.
+ """
+ try:
+ guess = self.question.guess(interaction.user.id, self.label)
+ except AlreadyUpdated:
+ await interaction.response.send_message(
+ embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="You've already changed your answer more than once!",
+ color=Colours.soft_red
+ ),
+ ephemeral=True
+ )
+ return
+ except QuestionClosed:
+ await interaction.response.send_message(
+ embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="The question is no longer accepting guesses!",
+ color=Colours.soft_red
+ ),
+ ephemeral=True
+ )
+ return
+
+ if guess[1]:
+ await interaction.response.send_message(
+ embed=Embed(
+ title="Confirming that...",
+ description=f"You chose answer {self.label}.",
+ color=Colours.soft_green
+ ),
+ ephemeral=True
+ )
+ else:
+ # guess[1] is False and they cannot change their answer again. Which
+ # indicates that they changed it this time around.
+ await interaction.response.send_message(
+ embed=Embed(
+ title="Confirming that...",
+ description=f"You changed your answer to answer {self.label}.",
+ color=Colours.soft_green
+ ),
+ ephemeral=True
+ )
+
+
+class QuestionView(View):
+ """View for one trivia night question."""
+
+ def __init__(self, question: Question) -> None:
+ super().__init__()
+ self.question = question
+
+ for letter, _ in self.question.answers:
+ self.add_item(AnswerButton(letter, self.question))
+
+ @staticmethod
+ def unicodeify(text: str) -> str:
+ """
+ Takes `text` and adds zero-width spaces to prevent copy and pasting the question.
+
+ Parameters:
+ - text: A string that represents the question description to 'unicodeify'
+ """
+ return "".join(
+ f"{letter}\u200b" if letter not in ('\n', '\t', '`', 'p', 'y') else letter
+ for idx, letter in enumerate(text)
+ )
+
+ def create_embed(self) -> Embed:
+ """Helper function to create the embed for the current question."""
+ question_embed = Embed(
+ title=f"Question {self.question.number}",
+ description=self.unicodeify(self.question.description),
+ color=Colours.python_yellow
+ )
+
+ for label, answer in self.question.answers:
+ question_embed.add_field(name=f"Answer {label}", value=answer, inline=False)
+
+ return question_embed
+
+ def end_question(self, scoreboard: Scoreboard) -> Embed:
+ """
+ Ends the question and displays the statistics on who got the question correct, awards points, etc.
+
+ Returns:
+ An embed displaying the correct answers and the % of people that chose each answer.
+ """
+ guesses = self.question.stop()
+
+ labels = ascii_uppercase[:len(self.question.answers)]
+
+ answer_embed = Embed(
+ title=f"The correct answer for Question {self.question.number} was...",
+ description=self.question.correct
+ )
+
+ if len(guesses) != 0:
+ answers_chosen = {
+ answer_choice: len(
+ tuple(filter(lambda x: x[0] == answer_choice, guesses.values()))
+ )
+ for answer_choice in labels
+ }
+
+ answers_chosen = dict(
+ sorted(list(answers_chosen.items()), key=lambda item: item[1], reverse=True)
+ )
+
+ for answer, people_answered in answers_chosen.items():
+ is_correct_answer = dict(self.question.answers)[answer[0]] == self.question.correct
+
+ # Setting the color of answer_embed to the % of people that got it correct via the mapping
+ if is_correct_answer:
+ # Maps the % of people who got it right to a color, from a range of red to green
+ percentage_to_color = [0xFC94A1, 0xFFCCCB, 0xCDFFCC, 0xB0F5AB, 0xB0F5AB]
+ answer_embed.color = percentage_to_color[round(people_answered / len(guesses) * 100) // 25]
+
+ field_title = (
+ (":white_check_mark: " if is_correct_answer else "")
+ + f"{people_answered} players ({people_answered / len(guesses) * 100:.1f}%) chose"
+ )
+
+ # The `ord` function is used here to change the letter to its corresponding position
+ answer_embed.add_field(
+ name=field_title,
+ value=self.question.answers[ord(answer) - 65][1],
+ inline=False
+ )
+
+ # Assign points to users
+ for user_id, answer in guesses.items():
+ if dict(self.question.answers)[answer[0]] == self.question.correct:
+ scoreboard.assign_points(
+ int(user_id),
+ points=(1 - (answer[-1] / self.question.time) / 2) * self.question.max_points,
+ speed=answer[-1]
+ )
+ elif answer[-1] <= 2:
+ scoreboard.assign_points(
+ int(user_id),
+ points=-(1 - (answer[-1] / self.question.time) / 2) * self.question.max_points
+ )
+ else:
+ scoreboard.assign_points(
+ int(user_id),
+ points=0
+ )
+
+ return answer_embed
diff --git a/bot/exts/events/trivianight/_scoreboard.py b/bot/exts/events/trivianight/_scoreboard.py
new file mode 100644
index 00000000..a5a5fcac
--- /dev/null
+++ b/bot/exts/events/trivianight/_scoreboard.py
@@ -0,0 +1,186 @@
+from random import choice
+
+import discord.ui
+from discord import ButtonStyle, Embed, Interaction, Member
+from discord.ui import Button, View
+
+from bot.bot import Bot
+from bot.constants import Colours, NEGATIVE_REPLIES
+
+
+class ScoreboardView(View):
+ """View for the scoreboard."""
+
+ def __init__(self, bot: Bot):
+ super().__init__()
+ self.bot = bot
+
+ @staticmethod
+ def _int_to_ordinal(number: int) -> str:
+ """
+ Converts an integer into an ordinal number, i.e. 1 to 1st.
+
+ Parameters:
+ - number: an integer representing the number to convert to an ordinal number.
+ """
+ suffix = ["th", "st", "nd", "rd", "th"][min(number % 10, 4)]
+ if (number % 100) in {11, 12, 13}:
+ suffix = "th"
+
+ return str(number) + suffix
+
+ async def create_main_leaderboard(self) -> Embed:
+ """
+ Helper function that iterates through `self.points` to generate the main leaderboard embed.
+
+ The main leaderboard would be formatted like the following:
+ **1**. @mention of the user (# of points)
+ along with the 29 other users who made it onto the leaderboard.
+ """
+ formatted_string = ""
+
+ for current_placement, (user, points) in enumerate(self.points.items()):
+ if current_placement + 1 > 30:
+ break
+
+ user = await self.bot.fetch_user(int(user))
+ formatted_string += f"**{current_placement + 1}.** {user.mention} "
+ formatted_string += f"({points:.1f} pts)\n"
+ if (current_placement + 1) % 10 == 0:
+ formatted_string += "⎯⎯⎯⎯⎯⎯⎯⎯\n"
+
+ main_embed = Embed(
+ title="Winners of the Trivia Night",
+ description=formatted_string,
+ color=Colours.python_blue,
+ )
+
+ return main_embed
+
+ async def _create_speed_embed(self) -> Embed:
+ """
+ Helper function that iterates through `self.speed` to generate a leaderboard embed.
+
+ The speed leaderboard would be formatted like the following:
+ **1**. @mention of the user ([average speed as a float with the precision of one decimal point]s)
+ along with the 29 other users who made it onto the leaderboard.
+ """
+ formatted_string = ""
+
+ for current_placement, (user, time_taken) in enumerate(self.speed.items()):
+ if current_placement + 1 > 30:
+ break
+
+ user = await self.bot.fetch_user(int(user))
+ formatted_string += f"**{current_placement + 1}.** {user.mention} "
+ formatted_string += f"({(time_taken[-1] / time_taken[0]):.1f}s)\n"
+ if (current_placement + 1) % 10 == 0:
+ formatted_string += "⎯⎯⎯⎯⎯⎯⎯⎯\n"
+
+ speed_embed = Embed(
+ title="Average time taken to answer a question",
+ description=formatted_string,
+ color=Colours.python_blue
+ )
+ return speed_embed
+
+ def _get_rank(self, member: Member) -> Embed:
+ """
+ Gets the member's rank for the points leaderboard and speed leaderboard.
+
+ Parameters:
+ - member: An instance of discord.Member representing the person who is trying to get their rank.
+ """
+ rank_embed = Embed(title=f"Ranks for {member.display_name}", color=Colours.python_blue)
+ # These are stored as strings so that the last digit can be determined to choose the suffix
+ try:
+ points_rank = str(list(self.points).index(member.id) + 1)
+ speed_rank = str(list(self.speed).index(member.id) + 1)
+ except ValueError:
+ return Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="It looks like you didn't participate in the Trivia Night event!",
+ color=Colours.soft_red
+ )
+
+ rank_embed.add_field(
+ name="Total Points",
+ value=(
+ f"You got {self._int_to_ordinal(int(points_rank))} place"
+ f" with {self.points[member.id]:.1f} points."
+ ),
+ inline=False
+ )
+
+ rank_embed.add_field(
+ name="Average Speed",
+ value=(
+ f"You got {self._int_to_ordinal(int(speed_rank))} place"
+ f" with a time of {(self.speed[member.id][1] / self.speed[member.id][0]):.1f} seconds."
+ ),
+ inline=False
+ )
+ return rank_embed
+
+ @discord.ui.button(label="Scoreboard for Speed", style=ButtonStyle.green)
+ async def speed_leaderboard(self, button: Button, interaction: Interaction) -> None:
+ """
+ Send an ephemeral message with the speed leaderboard embed.
+
+ Parameters:
+ - button: The discord.ui.Button instance representing the `Speed Leaderboard` button.
+ - interaction: The discord.Interaction instance containing information on the interaction between the user
+ and the button.
+ """
+ await interaction.response.send_message(embed=await self._create_speed_embed(), ephemeral=True)
+
+ @discord.ui.button(label="What's my rank?", style=ButtonStyle.blurple)
+ async def rank_button(self, button: Button, interaction: Interaction) -> None:
+ """
+ Send an ephemeral message with the user's rank for the overall points/average speed.
+
+ Parameters:
+ - button: The discord.ui.Button instance representing the `What's my rank?` button.
+ - interaction: The discord.Interaction instance containing information on the interaction between the user
+ and the button.
+ """
+ await interaction.response.send_message(embed=self._get_rank(interaction.user), ephemeral=True)
+
+
+class Scoreboard:
+ """Class for the scoreboard for the Trivia Night event."""
+
+ def __init__(self, bot: Bot):
+ self._bot = bot
+ self._points = {}
+ self._speed = {}
+
+ def assign_points(self, user_id: int, *, points: int = None, speed: float = None) -> None:
+ """
+ Assign points or deduct points to/from a certain user.
+
+ This method should be called once the question has finished and all answers have been registered.
+ """
+ if points is not None and user_id not in self._points.keys():
+ self._points[user_id] = points
+ elif points is not None:
+ self._points[user_id] += points
+
+ if speed is not None and user_id not in self._speed.keys():
+ self._speed[user_id] = [1, speed]
+ elif speed is not None:
+ self._speed[user_id] = [
+ self._speed[user_id][0] + 1, self._speed[user_id][1] + speed
+ ]
+
+ async def display(self, speed_leaderboard: bool = False) -> tuple[Embed, View]:
+ """Returns the embed of the main leaderboard along with the ScoreboardView."""
+ view = ScoreboardView(self._bot)
+
+ view.points = dict(sorted(self._points.items(), key=lambda item: item[-1], reverse=True))
+ view.speed = dict(sorted(self._speed.items(), key=lambda item: item[-1][1] / item[-1][0]))
+
+ return (
+ await view.create_main_leaderboard(),
+ view if not speed_leaderboard else await view._create_speed_embed()
+ )
diff --git a/bot/exts/events/trivianight/trivianight.py b/bot/exts/events/trivianight/trivianight.py
new file mode 100644
index 00000000..18d8327a
--- /dev/null
+++ b/bot/exts/events/trivianight/trivianight.py
@@ -0,0 +1,328 @@
+import asyncio
+from json import JSONDecodeError, loads
+from random import choice
+from typing import Optional
+
+from discord import Embed
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours, NEGATIVE_REPLIES, POSITIVE_REPLIES, Roles
+from bot.utils.pagination import LinePaginator
+
+from ._game import AllQuestionsVisited, TriviaNightGame
+from ._questions import QuestionView
+from ._scoreboard import Scoreboard
+
+# The ID you see below are the Events Lead role ID and the Event Runner Role ID
+TRIVIA_NIGHT_ROLES = (Roles.admins, 778361735739998228, 940911658799333408)
+
+
+class TriviaNightCog(commands.Cog):
+ """Cog for the Python Trivia Night event."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+ self.game: Optional[TriviaNightGame] = None
+ self.scoreboard: Optional[Scoreboard] = None
+ self.question_closed: asyncio.Event = None
+
+ @commands.group(aliases=["tn"], invoke_without_command=True)
+ async def trivianight(self, ctx: commands.Context) -> None:
+ """
+ The command group for the Python Discord Trivia Night.
+
+ If invoked without a subcommand (i.e. simply .trivianight), it will explain what the Trivia Night event is.
+ """
+ cog_description = Embed(
+ title="What is .trivianight?",
+ description=(
+ "This 'cog' is for the Python Discord's TriviaNight (date tentative)! Compete against other"
+ " players in a trivia about Python!"
+ ),
+ color=Colours.soft_green
+ )
+ await ctx.send(embed=cog_description)
+
+ @trivianight.command()
+ @commands.has_any_role(*TRIVIA_NIGHT_ROLES)
+ async def load(self, ctx: commands.Context, *, to_load: Optional[str]) -> None:
+ """
+ Loads a JSON file from the provided attachment or argument.
+
+ The JSON provided is formatted where it is a list of dictionaries, each dictionary containing the keys below:
+ - number: int (represents the current question #)
+ - description: str (represents the question itself)
+ - answers: list[str] (represents the different answers possible, must be a length of 4)
+ - correct: str (represents the correct answer in terms of what the correct answer is in `answers`
+ - time: Optional[int] (represents the timer for the question and how long it should run, default is 10)
+ - points: Optional[int] (represents how many points are awarded for each question, default is 10)
+
+ The load command accepts three different ways of loading in a JSON:
+ - an attachment of the JSON file
+ - a message link to the attachment/JSON
+ - reading the JSON itself via a codeblock or plain text
+ """
+ if self.game is not None:
+ await ctx.send(embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is already a trivia night running!",
+ color=Colours.soft_red
+ ))
+ return
+
+ if ctx.message.attachments:
+ json_text = (await ctx.message.attachments[0].read()).decode("utf8")
+ elif not to_load:
+ raise commands.BadArgument("You didn't attach an attachment nor link a message!")
+ elif (
+ to_load.startswith("https://discord.com/channels")
+ or to_load.startswith("https://discordapp.com/channels")
+ ):
+ channel_id, message_id = to_load.split("/")[-2:]
+ channel = await ctx.guild.fetch_channel(int(channel_id))
+ message = await channel.fetch_message(int(message_id))
+ if message.attachments:
+ json_text = (await message.attachments[0].read()).decode("utf8")
+ else:
+ json_text = message.content.replace("```", "").replace("json", "").replace("\n", "")
+ else:
+ json_text = to_load.replace("```", "").replace("json", "").replace("\n", "")
+
+ try:
+ serialized_json = loads(json_text)
+ except JSONDecodeError as error:
+ raise commands.BadArgument(f"Looks like something went wrong:\n{str(error)}")
+
+ self.game = TriviaNightGame(serialized_json)
+ self.question_closed = asyncio.Event()
+
+ success_embed = Embed(
+ title=choice(POSITIVE_REPLIES),
+ description="The JSON was loaded successfully!",
+ color=Colours.soft_green
+ )
+
+ self.scoreboard = Scoreboard(self.bot)
+
+ await ctx.send(embed=success_embed)
+
+ @trivianight.command(aliases=('next',))
+ @commands.has_any_role(*TRIVIA_NIGHT_ROLES)
+ async def question(self, ctx: commands.Context, question_number: str = None) -> None:
+ """
+ Gets a random question from the unanswered question list and lets the user(s) choose the answer.
+
+ This command will continuously count down until the time limit of the question is exhausted.
+ However, if `.trivianight stop` is invoked, the counting down is interrupted to show the final results.
+ """
+ if self.game is None:
+ await ctx.send(embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is no trivia night running!",
+ color=Colours.soft_red
+ ))
+ return
+
+ if self.game.current_question is not None:
+ error_embed = Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is already an ongoing question!",
+ color=Colours.soft_red
+ )
+ await ctx.send(embed=error_embed)
+ return
+
+ try:
+ next_question = self.game.next_question(question_number)
+ except AllQuestionsVisited:
+ error_embed = Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="All of the questions have been used.",
+ color=Colours.soft_red
+ )
+ await ctx.send(embed=error_embed)
+ return
+
+ await ctx.send("Next question in 3 seconds! Get ready...")
+ await asyncio.sleep(3)
+
+ question_view = QuestionView(next_question)
+ question_embed = question_view.create_embed()
+
+ next_question.start()
+ message = await ctx.send(embed=question_embed, view=question_view)
+
+ # Exponentially sleep less and less until the time limit is reached
+ percentage = 1
+ while True:
+ percentage *= 0.5
+ duration = next_question.time * percentage
+
+ await asyncio.wait([self.question_closed.wait()], timeout=duration)
+
+ if self.question_closed.is_set():
+ await ctx.send(embed=question_view.end_question(self.scoreboard))
+ await message.edit(embed=question_embed, view=None)
+
+ self.game.end_question()
+ self.question_closed.clear()
+ return
+
+ if int(duration) > 1:
+ # It is quite ugly to display decimals, the delay for requests to reach Discord
+ # cause sub-second accuracy to be quite pointless.
+ await ctx.send(f"{int(duration)}s remaining...")
+ else:
+ # Since each time we divide the percentage by 2 and sleep one half of the halves (then sleep a
+ # half, of that half) we must sleep both halves at the end.
+ await asyncio.wait([self.question_closed.wait()], timeout=duration)
+ if self.question_closed.is_set():
+ await ctx.send(embed=question_view.end_question(self.scoreboard))
+ await message.edit(embed=question_embed, view=None)
+
+ self.game.end_question()
+ self.question_closed.clear()
+ return
+ break
+
+ await ctx.send(embed=question_view.end_question(self.scoreboard))
+ await message.edit(embed=question_embed, view=None)
+
+ self.game.end_question()
+
+ @trivianight.command()
+ @commands.has_any_role(*TRIVIA_NIGHT_ROLES)
+ async def list(self, ctx: commands.Context) -> None:
+ """
+ Display all the questions left in the question bank.
+
+ Questions are displayed in the following format:
+ Q(number): Question description | :white_check_mark: if the question was used otherwise :x:.
+ """
+ if self.game is None:
+ await ctx.send(embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is no trivia night running!",
+ color=Colours.soft_red
+ ))
+ return
+
+ question_list = self.game.list_questions()
+
+ list_embed = Embed(title="All Trivia Night Questions")
+
+ if len(question_list) == 1:
+ list_embed.description = question_list[0]
+ await ctx.send(embed=list_embed)
+ else:
+ await LinePaginator.paginate(
+ question_list,
+ ctx,
+ list_embed
+ )
+
+ @trivianight.command()
+ @commands.has_any_role(*TRIVIA_NIGHT_ROLES)
+ async def stop(self, ctx: commands.Context) -> None:
+ """
+ End the ongoing question to show the correct question.
+
+ This command should be used if the question should be ended early or if the time limit fails
+ """
+ if self.game is None:
+ await ctx.send(embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is no trivia night running!",
+ color=Colours.soft_red
+ ))
+ return
+
+ if self.game.current_question is None:
+ error_embed = Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is no ongoing question!",
+ color=Colours.soft_red
+ )
+ await ctx.send(embed=error_embed)
+ return
+
+ self.question_closed.set()
+
+ @trivianight.command()
+ @commands.has_any_role(*TRIVIA_NIGHT_ROLES)
+ async def end(self, ctx: commands.Context) -> None:
+ """
+ Displays the scoreboard view.
+
+ The scoreboard view consists of the two scoreboards with the 30 players who got the highest points and the
+ 30 players who had the fastest average response time to a question where they got the question right.
+
+ The scoreboard view also has a button where the user can see their own rank, points and average speed if they
+ didn't make it onto the leaderboard.
+ """
+ if self.game is None:
+ await ctx.send(embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is no trivia night running!",
+ color=Colours.soft_red
+ ))
+ return
+
+ if self.game.current_question is not None:
+ error_embed = Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="You can't end the event while a question is ongoing!",
+ color=Colours.soft_red
+ )
+ await ctx.send(embed=error_embed)
+ return
+
+ scoreboard_embed, scoreboard_view = await self.scoreboard.display()
+ await ctx.send(embed=scoreboard_embed, view=scoreboard_view)
+
+ @trivianight.command()
+ @commands.has_any_role(*TRIVIA_NIGHT_ROLES)
+ async def scoreboard(self, ctx: commands.Context) -> None:
+ """
+ Displays the scoreboard.
+
+ The scoreboard consists of the two scoreboards with the 30 players who got the highest points and the
+ 30 players who had the fastest average response time to a question where they got the question right.
+ """
+ if self.game is None:
+ await ctx.send(embed=Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="There is no trivia night running!",
+ color=Colours.soft_red
+ ))
+ return
+
+ if self.game.current_question is not None:
+ error_embed = Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="You can't end the event while a question is ongoing!",
+ color=Colours.soft_red
+ )
+ await ctx.send(embed=error_embed)
+ return
+
+ scoreboard_embed, speed_scoreboard = await self.scoreboard.display(speed_leaderboard=True)
+ await ctx.send(embeds=(scoreboard_embed, speed_scoreboard))
+
+ @trivianight.command()
+ @commands.has_any_role(*TRIVIA_NIGHT_ROLES)
+ async def end_game(self, ctx: commands.Context) -> None:
+ """Ends the ongoing game."""
+ self.game = None
+
+ await ctx.send(embed=Embed(
+ title=choice(POSITIVE_REPLIES),
+ description="The game has been stopped.",
+ color=Colours.soft_green
+ ))
+
+
+def setup(bot: Bot) -> None:
+ """Load the TriviaNight cog."""
+ bot.add_cog(TriviaNightCog(bot))
diff --git a/bot/exts/fun/anagram.py b/bot/exts/fun/anagram.py
new file mode 100644
index 00000000..79280fa9
--- /dev/null
+++ b/bot/exts/fun/anagram.py
@@ -0,0 +1,109 @@
+import asyncio
+import json
+import logging
+import random
+from pathlib import Path
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours
+
+log = logging.getLogger(__name__)
+
+TIME_LIMIT = 60
+
+# anagram.json file contains all the anagrams
+with open(Path("bot/resources/fun/anagram.json"), "r") as f:
+ ANAGRAMS_ALL = json.load(f)
+
+
+class AnagramGame:
+ """
+ Used for creating instances of anagram games.
+
+ Once multiple games can be run at the same time, this class' instances
+ can be used for keeping track of each anagram game.
+ """
+
+ def __init__(self, scrambled: str, correct: list[str]) -> None:
+ self.scrambled = scrambled
+ self.correct = set(correct)
+
+ self.winners = set()
+
+ async def message_creation(self, message: discord.Message) -> None:
+ """Check if the message is a correct answer and remove it from the list of answers."""
+ if message.content.lower() in self.correct:
+ self.winners.add(message.author.mention)
+ self.correct.remove(message.content.lower())
+
+
+class Anagram(commands.Cog):
+ """Cog for the Anagram game command."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ self.games: dict[int, AnagramGame] = {}
+
+ @commands.command(name="anagram", aliases=("anag", "gram", "ag"))
+ async def anagram_command(self, ctx: commands.Context) -> None:
+ """
+ Given shuffled letters, rearrange them into anagrams.
+
+ Show an embed with scrambled letters which if rearranged can form words.
+ After a specific amount of time, list the correct answers and whether someone provided a
+ correct answer.
+ """
+ if self.games.get(ctx.channel.id):
+ await ctx.send("An anagram is already being solved in this channel!")
+ return
+
+ scrambled_letters, correct = random.choice(list(ANAGRAMS_ALL.items()))
+
+ game = AnagramGame(scrambled_letters, correct)
+ self.games[ctx.channel.id] = game
+
+ anagram_embed = discord.Embed(
+ title=f"Find anagrams from these letters: '{scrambled_letters.upper()}'",
+ description=f"You have {TIME_LIMIT} seconds to find correct words.",
+ colour=Colours.purple,
+ )
+
+ await ctx.send(embed=anagram_embed)
+ await asyncio.sleep(TIME_LIMIT)
+
+ if game.winners:
+ win_list = ", ".join(game.winners)
+ content = f"Well done {win_list} for getting it right!"
+ else:
+ content = "Nobody got it right."
+
+ answer_embed = discord.Embed(
+ title=f"The words were: `{'`, `'.join(ANAGRAMS_ALL[game.scrambled])}`!",
+ colour=Colours.pink,
+ )
+
+ await ctx.send(content, embed=answer_embed)
+
+ # Game is finished, let's remove it from the dict
+ self.games.pop(ctx.channel.id)
+
+ @commands.Cog.listener()
+ async def on_message(self, message: discord.Message) -> None:
+ """Check a message for an anagram attempt and pass to an ongoing game."""
+ if message.author.bot or not message.guild:
+ return
+
+ game = self.games.get(message.channel.id)
+ if not game:
+ return
+
+ await game.message_creation(message)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Anagram cog."""
+ bot.add_cog(Anagram(bot))
diff --git a/bot/exts/fun/battleship.py b/bot/exts/fun/battleship.py
index f4351954..beff196f 100644
--- a/bot/exts/fun/battleship.py
+++ b/bot/exts/fun/battleship.py
@@ -369,7 +369,6 @@ class Battleship(commands.Cog):
return any(player in (game.p1.user, game.p2.user) for game in self.games)
@commands.group(invoke_without_command=True)
- @commands.guild_only()
async def battleship(self, ctx: commands.Context) -> None:
"""
Play a game of Battleship with someone else!
diff --git a/bot/exts/fun/connect_four.py b/bot/exts/fun/connect_four.py
index 647bb2b7..f53695d5 100644
--- a/bot/exts/fun/connect_four.py
+++ b/bot/exts/fun/connect_four.py
@@ -6,7 +6,6 @@ from typing import Optional, Union
import discord
import emojis
from discord.ext import commands
-from discord.ext.commands import guild_only
from bot.bot import Bot
from bot.constants import Emojis
@@ -361,7 +360,6 @@ class ConnectFour(commands.Cog):
self.games.remove(game)
raise
- @guild_only()
@commands.group(
invoke_without_command=True,
aliases=("4inarow", "connect4", "connectfour", "c4"),
@@ -426,7 +424,6 @@ class ConnectFour(commands.Cog):
await self._play_game(ctx, user, board_size, str(emoji1), str(emoji2))
- @guild_only()
@connect_four.command(aliases=("bot", "computer", "cpu"))
async def ai(
self,
diff --git a/bot/exts/fun/duck_game.py b/bot/exts/fun/duck_game.py
index 1ef7513f..10b03a49 100644
--- a/bot/exts/fun/duck_game.py
+++ b/bot/exts/fun/duck_game.py
@@ -11,7 +11,7 @@ from PIL import Image, ImageDraw, ImageFont
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import Colours, MODERATION_ROLES
+from bot.constants import MODERATION_ROLES
from bot.utils.decorators import with_role
DECK = list(product(*[(0, 1, 2)]*4))
@@ -130,6 +130,9 @@ class DuckGame:
while len(self.solutions) < minimum_solutions:
self.board = random.sample(DECK, size)
+ self.board_msg = None
+ self.found_msg = None
+
@property
def board(self) -> list[tuple[int]]:
"""Accesses board property."""
@@ -181,7 +184,7 @@ class DuckGamesDirector(commands.Cog):
)
@commands.cooldown(rate=1, per=2, type=commands.BucketType.channel)
async def start_game(self, ctx: commands.Context) -> None:
- """Generate a board, send the game embed, and end the game after a time limit."""
+ """Start a new Duck Duck Duck Goose game."""
if ctx.channel.id in self.current_games:
await ctx.send("There's already a game running!")
return
@@ -191,8 +194,8 @@ class DuckGamesDirector(commands.Cog):
game.running = True
self.current_games[ctx.channel.id] = game
- game.msg_content = ""
- game.embed_msg = await self.send_board_embed(ctx, game)
+ game.board_msg = await self.send_board_embed(ctx, game)
+ game.found_msg = await self.send_found_embed(ctx)
await asyncio.sleep(GAME_DURATION)
# Checking for the channel ID in the currently running games is not sufficient.
@@ -245,13 +248,13 @@ class DuckGamesDirector(commands.Cog):
if answer in game.solutions:
game.claimed_answers[answer] = msg.author
game.scores[msg.author] += CORRECT_SOLN
- await self.display_claimed_answer(game, msg.author, answer)
+ await self.append_to_found_embed(game, f"{str(answer):12s} - {msg.author.display_name}")
else:
await msg.add_reaction(EMOJI_WRONG)
game.scores[msg.author] += INCORRECT_SOLN
async def send_board_embed(self, ctx: commands.Context, game: DuckGame) -> discord.Message:
- """Create and send the initial game embed. This will be edited as the game goes on."""
+ """Create and send an embed to display the board."""
image = assemble_board_image(game.board, game.rows, game.columns)
with BytesIO() as image_stream:
image.save(image_stream, format="png")
@@ -259,19 +262,27 @@ class DuckGamesDirector(commands.Cog):
file = discord.File(fp=image_stream, filename="board.png")
embed = discord.Embed(
title="Duck Duck Duck Goose!",
- color=Colours.bright_green,
+ color=discord.Color.dark_purple(),
)
embed.set_image(url="attachment://board.png")
return await ctx.send(embed=embed, file=file)
- async def display_claimed_answer(self, game: DuckGame, author: discord.Member, answer: tuple[int]) -> None:
- """Add a claimed answer to the game embed."""
+ async def send_found_embed(self, ctx: commands.Context) -> discord.Message:
+ """Create and send an embed to display claimed answers. This will be edited as the game goes on."""
+ # Can't be part of the board embed because of discord.py limitations with editing an embed with an image.
+ embed = discord.Embed(
+ title="Flights Found",
+ color=discord.Color.dark_purple(),
+ )
+ return await ctx.send(embed=embed)
+
+ async def append_to_found_embed(self, game: DuckGame, text: str) -> None:
+ """Append text to the claimed answers embed."""
async with game.editing_embed:
- # We specifically edit the message contents instead of the embed
- # Because we load in the image from the file, editing any portion of the embed
- # Does weird things to the image and this works around that weirdness
- game.msg_content = f"{game.msg_content}\n{str(answer):12s} - {author.display_name}"
- await game.embed_msg.edit(content=game.msg_content)
+ found_embed, = game.found_msg.embeds
+ old_desc = found_embed.description or ""
+ found_embed.description = f"{old_desc.rstrip()}\n{text}"
+ await game.found_msg.edit(embed=found_embed)
async def end_game(self, channel: discord.TextChannel, game: DuckGame, end_message: str) -> None:
"""Edit the game embed to reflect the end of the game and mark the game as not running."""
@@ -296,8 +307,7 @@ class DuckGamesDirector(commands.Cog):
missed_text = "Flights everyone missed:\n" + "\n".join(f"{ans}" for ans in missed)
else:
missed_text = "All the flights were found!"
-
- await game.embed_msg.edit(content=f"{missed_text}")
+ await self.append_to_found_embed(game, f"\n{missed_text}")
@start_game.command(name="help")
async def show_rules(self, ctx: commands.Context) -> None:
diff --git a/bot/exts/fun/game.py b/bot/exts/fun/game.py
index f9c150e6..5f56bef7 100644
--- a/bot/exts/fun/game.py
+++ b/bot/exts/fun/game.py
@@ -118,6 +118,7 @@ class GameStatus(IntEnum):
Offline = 5
Cancelled = 6
Rumored = 7
+ Delisted = 8
class AgeRatingCategories(IntEnum):
@@ -125,6 +126,11 @@ class AgeRatingCategories(IntEnum):
ESRB = 1
PEGI = 2
+ CERO = 3
+ USK = 4
+ GRAC = 5
+ CLASS_IND = 6
+ ACB = 7
class AgeRatings(IntEnum):
@@ -142,6 +148,32 @@ class AgeRatings(IntEnum):
T = 10
M = 11
AO = 12
+ CERO_A = 13
+ CERO_B = 14
+ CERO_C = 15
+ CERO_D = 16
+ CERO_Z = 17
+ USK_0 = 18
+ USK_6 = 19
+ USK_12 = 20
+ USK_18 = 21
+ GRAC_ALL = 22
+ GRAC_Twelve = 23
+ GRAC_Fifteen = 24
+ GRAC_Eighteen = 25
+ GRAC_TESTING = 26
+ CLASS_IND_L = 27
+ CLASS_IND_Ten = 28
+ CLASS_IND_Twelve = 29
+ CLASS_IND_Fourteen = 30
+ CLASS_IND_Sixteen = 31
+ CLASS_IND_Eighteen = 32
+ ACB_G = 33
+ ACB_PG = 34
+ ACB_M = 35
+ ACB_MA15 = 36
+ ACB_R18 = 37
+ ACB_RC = 38
class Games(Cog):
diff --git a/bot/exts/fun/latex.py b/bot/exts/fun/latex.py
new file mode 100644
index 00000000..d43ec8c4
--- /dev/null
+++ b/bot/exts/fun/latex.py
@@ -0,0 +1,130 @@
+import hashlib
+import re
+import string
+from io import BytesIO
+from pathlib import Path
+from typing import BinaryIO, Optional
+
+import discord
+from PIL import Image
+from discord.ext import commands
+
+from bot.bot import Bot
+
+FORMATTED_CODE_REGEX = re.compile(
+ r"(?P<delim>(?P<block>```)|``?)" # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block
+ r"(?(block)(?:(?P<lang>[a-z]+)\n)?)" # if we're in a block, match optional language (only letters plus newline)
+ r"(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code
+ r"(?P<code>.*?)" # extract all code inside the markup
+ r"\s*" # any more whitespace before the end of the code markup
+ r"(?P=delim)", # match the exact same delimiter from the start again
+ re.DOTALL | re.IGNORECASE, # "." also matches newlines, case insensitive
+)
+
+LATEX_API_URL = "https://rtex.probablyaweb.site/api/v2"
+PASTEBIN_URL = "https://paste.pythondiscord.com"
+
+THIS_DIR = Path(__file__).parent
+CACHE_DIRECTORY = THIS_DIR / "_latex_cache"
+CACHE_DIRECTORY.mkdir(exist_ok=True)
+TEMPLATE = string.Template(Path("bot/resources/fun/latex_template.txt").read_text())
+
+PAD = 10
+
+
+def _prepare_input(text: str) -> str:
+ """Extract latex from a codeblock, if it is in one."""
+ if match := FORMATTED_CODE_REGEX.match(text):
+ return match.group("code")
+ else:
+ return text
+
+
+def _process_image(data: bytes, out_file: BinaryIO) -> None:
+ """Read `data` as an image file, and paste it on a white background."""
+ image = Image.open(BytesIO(data)).convert("RGBA")
+ width, height = image.size
+ background = Image.new("RGBA", (width + 2 * PAD, height + 2 * PAD), "WHITE")
+
+ # paste the image on the background, using the same image as the mask
+ # when an RGBA image is passed as the mask, its alpha band is used.
+ # this has the effect of skipping pasting the pixels where the image is transparent.
+ background.paste(image, (PAD, PAD), image)
+ background.save(out_file)
+
+
+class InvalidLatexError(Exception):
+ """Represents an error caused by invalid latex."""
+
+ def __init__(self, logs: Optional[str]):
+ super().__init__(logs)
+ self.logs = logs
+
+
+class Latex(commands.Cog):
+ """Renders latex."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ async def _generate_image(self, query: str, out_file: BinaryIO) -> None:
+ """Make an API request and save the generated image to cache."""
+ payload = {"code": query, "format": "png"}
+ async with self.bot.http_session.post(LATEX_API_URL, data=payload, raise_for_status=True) as response:
+ response_json = await response.json()
+ if response_json["status"] != "success":
+ raise InvalidLatexError(logs=response_json.get("log"))
+ async with self.bot.http_session.get(
+ f"{LATEX_API_URL}/{response_json['filename']}",
+ raise_for_status=True
+ ) as response:
+ _process_image(await response.read(), out_file)
+
+ async def _upload_to_pastebin(self, text: str) -> Optional[str]:
+ """Uploads `text` to the paste service, returning the url if successful."""
+ try:
+ async with self.bot.http_session.post(
+ PASTEBIN_URL + "/documents",
+ data=text,
+ raise_for_status=True
+ ) as response:
+ response_json = await response.json()
+ if "key" in response_json:
+ return f"{PASTEBIN_URL}/{response_json['key']}.txt?noredirect"
+ except Exception:
+ # 400 (Bad Request) means there are too many characters
+ pass
+
+ @commands.command()
+ @commands.max_concurrency(1, commands.BucketType.guild, wait=True)
+ async def latex(self, ctx: commands.Context, *, query: str) -> None:
+ """Renders the text in latex and sends the image."""
+ query = _prepare_input(query)
+
+ # the hash of the query is used as the filename in the cache.
+ query_hash = hashlib.md5(query.encode()).hexdigest()
+ image_path = CACHE_DIRECTORY / f"{query_hash}.png"
+ async with ctx.typing():
+ if not image_path.exists():
+ try:
+ with open(image_path, "wb") as out_file:
+ await self._generate_image(TEMPLATE.substitute(text=query), out_file)
+ except InvalidLatexError as err:
+ embed = discord.Embed(title="Failed to render input.")
+ if err.logs is None:
+ embed.description = "No logs available."
+ else:
+ logs_paste_url = await self._upload_to_pastebin(err.logs)
+ if logs_paste_url:
+ embed.description = f"[View Logs]({logs_paste_url})"
+ else:
+ embed.description = "Couldn't upload logs."
+ await ctx.send(embed=embed)
+ image_path.unlink()
+ return
+ await ctx.send(file=discord.File(image_path, "latex.png"))
+
+
+def setup(bot: Bot) -> None:
+ """Load the Latex Cog."""
+ bot.add_cog(Latex(bot))
diff --git a/bot/exts/fun/madlibs.py b/bot/exts/fun/madlibs.py
new file mode 100644
index 00000000..21708e53
--- /dev/null
+++ b/bot/exts/fun/madlibs.py
@@ -0,0 +1,148 @@
+import json
+from asyncio import TimeoutError
+from pathlib import Path
+from random import choice
+from typing import TypedDict
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours, NEGATIVE_REPLIES
+
+TIMEOUT = 60.0
+
+
+class MadlibsTemplate(TypedDict):
+ """Structure of a template in the madlibs JSON file."""
+
+ title: str
+ blanks: list[str]
+ value: list[str]
+
+
+class Madlibs(commands.Cog):
+ """Cog for the Madlibs game."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+ self.templates = self._load_templates()
+ self.edited_content = {}
+ self.checks = set()
+
+ @staticmethod
+ def _load_templates() -> list[MadlibsTemplate]:
+ madlibs_stories = Path("bot/resources/fun/madlibs_templates.json")
+
+ with open(madlibs_stories) as file:
+ return json.load(file)
+
+ @staticmethod
+ def madlibs_embed(part_of_speech: str, number_of_inputs: int) -> discord.Embed:
+ """Method to generate an embed with the game information."""
+ madlibs_embed = discord.Embed(title="Madlibs", color=Colours.python_blue)
+
+ madlibs_embed.add_field(
+ name="Enter a word that fits the given part of speech!",
+ value=f"Part of speech: {part_of_speech}\n\nMake sure not to spam, or you may get auto-muted!"
+ )
+
+ madlibs_embed.set_footer(text=f"Inputs remaining: {number_of_inputs}")
+
+ return madlibs_embed
+
+ @commands.Cog.listener()
+ async def on_message_edit(self, _: discord.Message, after: discord.Message) -> None:
+ """A listener that checks for message edits from the user."""
+ for check in self.checks:
+ if check(after):
+ break
+ else:
+ return
+
+ self.edited_content[after.id] = after.content
+
+ @commands.command()
+ @commands.max_concurrency(1, per=commands.BucketType.user)
+ async def madlibs(self, ctx: commands.Context) -> None:
+ """
+ Play Madlibs with the bot!
+
+ Madlibs is a game where the player is asked to enter a word that
+ fits a random part of speech (e.g. noun, adjective, verb, plural noun, etc.)
+ a random amount of times, depending on the story chosen by the bot at the beginning.
+ """
+ random_template = choice(self.templates)
+
+ def author_check(message: discord.Message) -> bool:
+ return message.channel.id == ctx.channel.id and message.author.id == ctx.author.id
+
+ self.checks.add(author_check)
+
+ loading_embed = discord.Embed(
+ title="Madlibs", description="Loading your Madlibs game...", color=Colours.python_blue
+ )
+ original_message = await ctx.send(embed=loading_embed)
+
+ submitted_words = {}
+
+ for i, part_of_speech in enumerate(random_template["blanks"]):
+ inputs_left = len(random_template["blanks"]) - i
+
+ madlibs_embed = self.madlibs_embed(part_of_speech, inputs_left)
+ await original_message.edit(embed=madlibs_embed)
+
+ try:
+ message = await self.bot.wait_for(event="message", check=author_check, timeout=TIMEOUT)
+ except TimeoutError:
+ timeout_embed = discord.Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="Uh oh! You took too long to respond!",
+ color=Colours.soft_red
+ )
+
+ await ctx.send(ctx.author.mention, embed=timeout_embed)
+
+ for msg_id in submitted_words:
+ self.edited_content.pop(msg_id, submitted_words[msg_id])
+
+ self.checks.remove(author_check)
+
+ return
+
+ submitted_words[message.id] = message.content
+
+ blanks = [self.edited_content.pop(msg_id, submitted_words[msg_id]) for msg_id in submitted_words]
+
+ self.checks.remove(author_check)
+
+ story = []
+ for value, blank in zip(random_template["value"], blanks):
+ story.append(f"{value}__{blank}__")
+
+ # In each story template, there is always one more "value"
+ # (fragment from the story) than there are blanks (words that the player enters)
+ # so we need to compensate by appending the last line of the story again.
+ story.append(random_template["value"][-1])
+
+ story_embed = discord.Embed(
+ title=random_template["title"],
+ description="".join(story),
+ color=Colours.bright_green
+ )
+
+ story_embed.set_footer(text=f"Generated for {ctx.author}", icon_url=ctx.author.display_avatar.url)
+
+ await ctx.send(embed=story_embed)
+
+ @madlibs.error
+ async def handle_madlibs_error(self, ctx: commands.Context, error: commands.CommandError) -> None:
+ """Error handler for the Madlibs command."""
+ if isinstance(error, commands.MaxConcurrencyReached):
+ await ctx.send("You are already playing Madlibs!")
+ error.handled = True
+
+
+def setup(bot: Bot) -> None:
+ """Load the Madlibs cog."""
+ bot.add_cog(Madlibs(bot))
diff --git a/bot/exts/fun/quack.py b/bot/exts/fun/quack.py
new file mode 100644
index 00000000..0c228aed
--- /dev/null
+++ b/bot/exts/fun/quack.py
@@ -0,0 +1,75 @@
+import logging
+import random
+from typing import Literal, Optional
+
+import discord
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours, NEGATIVE_REPLIES
+
+API_URL = 'https://quackstack.pythondiscord.com'
+
+log = logging.getLogger(__name__)
+
+
+class Quackstack(commands.Cog):
+ """Cog used for wrapping Quackstack."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ @commands.command()
+ async def quack(
+ self,
+ ctx: commands.Context,
+ ducktype: Literal["duck", "manduck"] = "duck",
+ *,
+ seed: Optional[str] = None
+ ) -> None:
+ """
+ Use the Quackstack API to generate a random duck.
+
+ If a seed is provided, a duck is generated based on the given seed.
+ Either "duck" or "manduck" can be provided to change the duck type generated.
+ """
+ ducktype = ducktype.lower()
+ quackstack_url = f"{API_URL}/{ducktype}"
+ params = {}
+ if seed is not None:
+ try:
+ seed = int(seed)
+ except ValueError:
+ # We just need to turn the string into an integer any way possible
+ seed = int.from_bytes(seed.encode(), "big")
+ params["seed"] = seed
+
+ async with self.bot.http_session.get(quackstack_url, params=params) as response:
+ error_embed = discord.Embed(
+ title=random.choice(NEGATIVE_REPLIES),
+ description="The request failed. Please try again later.",
+ color=Colours.soft_red,
+ )
+ if response.status != 200:
+ log.error(f"Response to Quackstack returned code {response.status}")
+ await ctx.send(embed=error_embed)
+ return
+
+ data = await response.json()
+ file = data["file"]
+
+ embed = discord.Embed(
+ title=f"Quack! Here's a {ducktype} for you.",
+ description=f"A {ducktype} from Quackstack.",
+ color=Colours.grass_green,
+ url=f"{API_URL}/docs"
+ )
+
+ embed.set_image(url=API_URL + file)
+
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot) -> None:
+ """Loads the Quack cog."""
+ bot.add_cog(Quackstack(bot))
diff --git a/bot/exts/fun/snakes/_utils.py b/bot/exts/fun/snakes/_utils.py
index de51339d..182fa9d9 100644
--- a/bot/exts/fun/snakes/_utils.py
+++ b/bot/exts/fun/snakes/_utils.py
@@ -6,13 +6,14 @@ import math
import random
from itertools import product
from pathlib import Path
+from typing import Union
from PIL import Image
from PIL.ImageDraw import ImageDraw
-from discord import File, Member, Reaction
+from discord import File, Member, Reaction, User
from discord.ext.commands import Cog, Context
-from bot.constants import Roles
+from bot.constants import MODERATION_ROLES
SNAKE_RESOURCES = Path("bot/resources/fun/snakes").absolute()
@@ -395,7 +396,7 @@ class SnakeAndLaddersGame:
Listen for reactions until players have joined, and the game has been started.
"""
- def startup_event_check(reaction_: Reaction, user_: Member) -> bool:
+ def startup_event_check(reaction_: Reaction, user_: Union[User, Member]) -> bool:
"""Make sure that this reaction is what we want to operate on."""
return (
all((
@@ -460,7 +461,7 @@ class SnakeAndLaddersGame:
await self.cancel_game()
return # We're done, no reactions for the last 5 minutes
- async def _add_player(self, user: Member) -> None:
+ async def _add_player(self, user: Union[User, Member]) -> None:
"""Add player to game."""
self.players.append(user)
self.player_tiles[user.id] = 1
@@ -469,7 +470,7 @@ class SnakeAndLaddersGame:
im = Image.open(io.BytesIO(avatar_bytes)).resize((BOARD_PLAYER_SIZE, BOARD_PLAYER_SIZE))
self.avatar_images[user.id] = im
- async def player_join(self, user: Member) -> None:
+ async def player_join(self, user: Union[User, Member]) -> None:
"""
Handle players joining the game.
@@ -495,7 +496,7 @@ class SnakeAndLaddersGame:
delete_after=10
)
- async def player_leave(self, user: Member) -> bool:
+ async def player_leave(self, user: Union[User, Member]) -> bool:
"""
Handle players leaving the game.
@@ -530,7 +531,7 @@ class SnakeAndLaddersGame:
await self.channel.send("**Snakes and Ladders**: Game has been canceled.")
self._destruct()
- async def start_game(self, user: Member) -> None:
+ async def start_game(self, user: Union[User, Member]) -> None:
"""
Allow the game author to begin the game.
@@ -551,7 +552,7 @@ class SnakeAndLaddersGame:
async def start_round(self) -> None:
"""Begin the round."""
- def game_event_check(reaction_: Reaction, user_: Member) -> bool:
+ def game_event_check(reaction_: Reaction, user_: Union[User, Member]) -> bool:
"""Make sure that this reaction is what we want to operate on."""
return (
all((
@@ -644,7 +645,7 @@ class SnakeAndLaddersGame:
if not is_surrendered:
await self._complete_round()
- async def player_roll(self, user: Member) -> None:
+ async def player_roll(self, user: Union[User, Member]) -> None:
"""Handle the player's roll."""
if user.id not in self.player_tiles:
await self.channel.send(user.mention + " You are not in the match.", delete_after=10)
@@ -691,7 +692,7 @@ class SnakeAndLaddersGame:
await self.channel.send("**Snakes and Ladders**: " + winner.mention + " has won the game! :tada:")
self._destruct()
- def _check_winner(self) -> Member:
+ def _check_winner(self) -> Union[User, Member]:
"""Return a winning member if we're in the post-round state and there's a winner."""
if self.state != "post_round":
return None
@@ -716,6 +717,6 @@ class SnakeAndLaddersGame:
return x_level, y_level
@staticmethod
- def _is_moderator(user: Member) -> bool:
+ def _is_moderator(user: Union[User, Member]) -> bool:
"""Return True if the user is a Moderator."""
- return any(Roles.moderator == role.id for role in user.roles)
+ return any(role.id in MODERATION_ROLES for role in getattr(user, 'roles', []))
diff --git a/bot/exts/fun/tic_tac_toe.py b/bot/exts/fun/tic_tac_toe.py
index 5c4f8051..5dd38a81 100644
--- a/bot/exts/fun/tic_tac_toe.py
+++ b/bot/exts/fun/tic_tac_toe.py
@@ -3,7 +3,7 @@ import random
from typing import Callable, Optional, Union
import discord
-from discord.ext.commands import Cog, Context, check, group, guild_only
+from discord.ext.commands import Cog, Context, check, group
from bot.bot import Bot
from bot.constants import Emojis
@@ -72,10 +72,12 @@ class Player:
class AI:
"""Tic Tac Toe AI class for against computer gaming."""
- def __init__(self, symbol: str):
+ def __init__(self, bot_user: discord.Member, symbol: str):
+ self.user = bot_user
self.symbol = symbol
- async def get_move(self, board: dict[int, str], _: discord.Message) -> tuple[bool, int]:
+ @staticmethod
+ async def get_move(board: dict[int, str], _: discord.Message) -> tuple[bool, int]:
"""Get move from AI. AI use Minimax strategy."""
possible_moves = [i for i, emoji in board.items() if emoji in list(Emojis.number_emojis.values())]
@@ -97,8 +99,8 @@ class AI:
return False, random.choice(open_edges)
def __str__(self) -> str:
- """Return `AI` as user name."""
- return "AI"
+ """Return mention of @Sir Lancebot."""
+ return self.user.mention
class Game:
@@ -107,6 +109,7 @@ class Game:
def __init__(self, players: list[Union[Player, AI]], ctx: Context):
self.players = players
self.ctx = ctx
+ self.channel = ctx.channel
self.board = {
1: Emojis.number_emojis[1],
2: Emojis.number_emojis[2],
@@ -173,7 +176,8 @@ class Game:
self.canceled = True
return False, "User declined"
- async def add_reactions(self, msg: discord.Message) -> None:
+ @staticmethod
+ async def add_reactions(msg: discord.Message) -> None:
"""Add number emojis to message."""
for nr in Emojis.number_emojis.values():
await msg.add_reaction(nr)
@@ -249,7 +253,6 @@ class TicTacToe(Cog):
def __init__(self):
self.games: list[Game] = []
- @guild_only()
@is_channel_free()
@is_requester_free()
@group(name="tictactoe", aliases=("ttt", "tic"), invoke_without_command=True)
@@ -265,7 +268,7 @@ class TicTacToe(Cog):
return
if opponent is None:
game = Game(
- [Player(ctx.author, ctx, Emojis.x_square), AI(Emojis.o_square)],
+ [Player(ctx.author, ctx, Emojis.x_square), AI(ctx.me, Emojis.o_square)],
ctx
)
else:
diff --git a/bot/exts/fun/trivia_quiz.py b/bot/exts/fun/trivia_quiz.py
index 236586b0..4a1cec5b 100644
--- a/bot/exts/fun/trivia_quiz.py
+++ b/bot/exts/fun/trivia_quiz.py
@@ -16,7 +16,7 @@ from discord.ext import commands, tasks
from rapidfuzz import fuzz
from bot.bot import Bot
-from bot.constants import Colours, NEGATIVE_REPLIES, Roles
+from bot.constants import Client, Colours, MODERATION_ROLES, NEGATIVE_REPLIES
logger = logging.getLogger(__name__)
@@ -332,7 +332,7 @@ class TriviaQuiz(commands.Cog):
if self.game_status[ctx.channel.id]:
await ctx.send(
"Game is already running... "
- f"do `{self.bot.command_prefix}quiz stop`"
+ f"do `{Client.prefix}quiz stop`"
)
return
@@ -550,7 +550,7 @@ class TriviaQuiz(commands.Cog):
if self.game_status[ctx.channel.id]:
# Check if the author is the game starter or a moderator.
if ctx.author == self.game_owners[ctx.channel.id] or any(
- Roles.moderator == role.id for role in ctx.author.roles
+ role.id in MODERATION_ROLES for role in getattr(ctx.author, 'roles', [])
):
self.game_status[ctx.channel.id] = False
del self.game_owners[ctx.channel.id]
diff --git a/bot/exts/holidays/easter/earth_photos.py b/bot/exts/holidays/easter/earth_photos.py
index f65790af..27442f1c 100644
--- a/bot/exts/holidays/easter/earth_photos.py
+++ b/bot/exts/holidays/easter/earth_photos.py
@@ -4,8 +4,7 @@ import discord
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import Colours
-from bot.constants import Tokens
+from bot.constants import Colours, Tokens
log = logging.getLogger(__name__)
diff --git a/bot/exts/holidays/easter/egg_facts.py b/bot/exts/holidays/easter/egg_facts.py
index 5f216e0d..152af6a4 100644
--- a/bot/exts/holidays/easter/egg_facts.py
+++ b/bot/exts/holidays/easter/egg_facts.py
@@ -31,7 +31,7 @@ class EasterFacts(commands.Cog):
"""A background task that sends an easter egg fact in the event channel everyday."""
await self.bot.wait_until_guild_available()
- channel = self.bot.get_channel(Channels.community_bot_commands)
+ channel = self.bot.get_channel(Channels.sir_lancebot_playground)
await channel.send(embed=self.make_embed())
@commands.command(name="eggfact", aliases=("fact",))
diff --git a/bot/exts/holidays/halloween/candy_collection.py b/bot/exts/holidays/halloween/candy_collection.py
index 4afd5913..220ba8e5 100644
--- a/bot/exts/holidays/halloween/candy_collection.py
+++ b/bot/exts/holidays/halloween/candy_collection.py
@@ -55,7 +55,7 @@ class CandyCollection(commands.Cog):
if message.author.bot:
return
# ensure it's hacktober channel
- if message.channel.id != Channels.community_bot_commands:
+ if message.channel.id != Channels.sir_lancebot_playground:
return
# do random check for skull first as it has the lower chance
@@ -77,12 +77,17 @@ class CandyCollection(commands.Cog):
return
# check to ensure it is in correct channel
- if message.channel.id != Channels.community_bot_commands:
+ if message.channel.id != Channels.sir_lancebot_playground:
return
# if its not a candy or skull, and it is one of 10 most recent messages,
# proceed to add a skull/candy with higher chance
if str(reaction.emoji) not in (EMOJIS["SKULL"], EMOJIS["CANDY"]):
+ # Ensure the reaction is not for a bot's message so users can't spam
+ # reaction buttons like in .help to get candies.
+ if message.author.bot:
+ return
+
recent_message_ids = map(
lambda m: m.id,
await self.hacktober_channel.history(limit=10).flatten()
@@ -134,7 +139,7 @@ class CandyCollection(commands.Cog):
@property
def hacktober_channel(self) -> discord.TextChannel:
"""Get #hacktoberbot channel from its ID."""
- return self.bot.get_channel(id=Channels.community_bot_commands)
+ return self.bot.get_channel(Channels.sir_lancebot_playground)
@staticmethod
async def send_spook_msg(
@@ -182,13 +187,24 @@ class CandyCollection(commands.Cog):
for index, record in enumerate(top_five)
) if top_five else "No Candies"
- e = discord.Embed(colour=discord.Colour.blurple())
+ def get_user_candy_score() -> str:
+ for user_id, score in records:
+ if user_id == ctx.author.id:
+ return f"{ctx.author.mention}: {score}"
+ return f"{ctx.author.mention}: 0"
+
+ e = discord.Embed(colour=discord.Colour.og_blurple())
e.add_field(
name="Top Candy Records",
value=generate_leaderboard(),
inline=False
)
e.add_field(
+ name="Your Candy Score",
+ value=get_user_candy_score(),
+ inline=False
+ )
+ e.add_field(
name="\u200b",
value="Candies will randomly appear on messages sent. "
"\nHit the candy when it appears as fast as possible to get the candy! "
diff --git a/bot/exts/holidays/halloween/scarymovie.py b/bot/exts/holidays/halloween/scarymovie.py
index 33659fd8..89310b97 100644
--- a/bot/exts/holidays/halloween/scarymovie.py
+++ b/bot/exts/holidays/halloween/scarymovie.py
@@ -6,6 +6,7 @@ from discord.ext import commands
from bot.bot import Bot
from bot.constants import Tokens
+
log = logging.getLogger(__name__)
diff --git a/bot/exts/holidays/halloween/spookynamerate.py b/bot/exts/holidays/halloween/spookynamerate.py
index 2e59d4a8..02fb71c3 100644
--- a/bot/exts/holidays/halloween/spookynamerate.py
+++ b/bot/exts/holidays/halloween/spookynamerate.py
@@ -143,7 +143,7 @@ class SpookyNameRate(Cog):
if data["author"] == ctx.author.id:
await ctx.send(
"But you have already added an entry! Type "
- f"`{self.bot.command_prefix}spookynamerate "
+ f"`{Client.prefix}spookynamerate "
"delete` to delete it, and then you can add it again"
)
return
@@ -185,7 +185,7 @@ class SpookyNameRate(Cog):
return
await ctx.send(
- f"But you don't have an entry... :eyes: Type `{self.bot.command_prefix}spookynamerate add your entry`"
+ f"But you don't have an entry... :eyes: Type `{Client.prefix}spookynamerate add your entry`"
)
@Cog.listener()
@@ -223,9 +223,9 @@ class SpookyNameRate(Cog):
if self.first_time:
await channel.send(
"Okkey... Welcome to the **Spooky Name Rate Game**! It's a relatively simple game.\n"
- f"Everyday, a random name will be sent in <#{Channels.community_bot_commands}> "
+ f"Everyday, a random name will be sent in <#{Channels.sir_lancebot_playground}> "
"and you need to try and spookify it!\nRegister your name using "
- f"`{self.bot.command_prefix}spookynamerate add spookified name`"
+ f"`{Client.prefix}spookynamerate add spookified name`"
)
await self.data.set("first_time", False)
@@ -359,10 +359,10 @@ class SpookyNameRate(Cog):
"""Gets the sir-lancebot-channel after waiting until ready."""
await self.bot.wait_until_ready()
channel = self.bot.get_channel(
- Channels.community_bot_commands
- ) or await self.bot.fetch_channel(Channels.community_bot_commands)
+ Channels.sir_lancebot_playground
+ ) or await self.bot.fetch_channel(Channels.sir_lancebot_playground)
if not channel:
- logger.warning("Bot is unable to get the #seasonalbot-commands channel. Please check the channel ID.")
+ logger.warning("Bot is unable to get the #sir-lancebot-playground channel. Please check the channel ID.")
return channel
@staticmethod
diff --git a/bot/exts/holidays/halloween/spookyreact.py b/bot/exts/holidays/halloween/spookyreact.py
index 25e783f4..e228b91d 100644
--- a/bot/exts/holidays/halloween/spookyreact.py
+++ b/bot/exts/holidays/halloween/spookyreact.py
@@ -47,12 +47,12 @@ class SpookyReact(Cog):
Short-circuit helper check.
Return True if:
- * author is the bot
+ * author is a bot
* prefix is not None
"""
- # Check for self reaction
- if message.author == self.bot.user:
- log.debug(f"Ignoring reactions on self message. Message ID: {message.id}")
+ # Check if message author is a bot
+ if message.author.bot:
+ log.debug(f"Ignoring reactions on bot message. Message ID: {message.id}")
return True
# Check for command invocation
diff --git a/bot/exts/holidays/hanukkah/hanukkah_embed.py b/bot/exts/holidays/hanukkah/hanukkah_embed.py
index ac3eab7b..5767f91e 100644
--- a/bot/exts/holidays/hanukkah/hanukkah_embed.py
+++ b/bot/exts/holidays/hanukkah/hanukkah_embed.py
@@ -21,45 +21,41 @@ class HanukkahEmbed(commands.Cog):
def __init__(self, bot: Bot):
self.bot = bot
- self.hanukkah_days = []
- self.hanukkah_months = []
- self.hanukkah_years = []
+ self.hanukkah_dates: list[datetime.date] = []
- async def get_hanukkah_dates(self) -> list[str]:
+ def _parse_time_to_datetime(self, date: list[str]) -> datetime.datetime:
+ """Format the times provided by the api to datetime forms."""
+ try:
+ return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S%z")
+ except ValueError:
+ # there is a possibility of an event not having a time, just a day
+ # to catch this, we try again without time information
+ return datetime.datetime.strptime(date, "%Y-%m-%d")
+
+ async def fetch_hanukkah_dates(self) -> list[datetime.date]:
"""Gets the dates for hanukkah festival."""
- hanukkah_dates = []
+ # clear the datetime objects to prevent a memory link
+ self.hanukkah_dates = []
async with self.bot.http_session.get(HEBCAL_URL) as response:
json_data = await response.json()
festivals = json_data["items"]
for festival in festivals:
if festival["title"].startswith("Chanukah"):
date = festival["date"]
- hanukkah_dates.append(date)
- return hanukkah_dates
+ self.hanukkah_dates.append(self._parse_time_to_datetime(date).date())
+ return self.hanukkah_dates
@in_month(Month.NOVEMBER, Month.DECEMBER)
@commands.command(name="hanukkah", aliases=("chanukah",))
async def hanukkah_festival(self, ctx: commands.Context) -> None:
"""Tells you about the Hanukkah Festivaltime of festival, festival day, etc)."""
- hanukkah_dates = await self.get_hanukkah_dates()
- self.hanukkah_dates_split(hanukkah_dates)
- hanukkah_start_day = int(self.hanukkah_days[0])
- hanukkah_start_month = int(self.hanukkah_months[0])
- hanukkah_start_year = int(self.hanukkah_years[0])
- hanukkah_end_day = int(self.hanukkah_days[8])
- hanukkah_end_month = int(self.hanukkah_months[8])
- hanukkah_end_year = int(self.hanukkah_years[8])
-
- hanukkah_start = datetime.date(hanukkah_start_year, hanukkah_start_month, hanukkah_start_day)
- hanukkah_end = datetime.date(hanukkah_end_year, hanukkah_end_month, hanukkah_end_day)
+ hanukkah_dates = await self.fetch_hanukkah_dates()
+ start_day = hanukkah_dates[0]
+ end_day = hanukkah_dates[-1]
today = datetime.date.today()
- # today = datetime.date(2019, 12, 24) (for testing)
- day = str(today.day)
- month = str(today.month)
- year = str(today.year)
embed = Embed(title="Hanukkah", colour=Colours.blue)
- if day in self.hanukkah_days and month in self.hanukkah_months and year in self.hanukkah_years:
- if int(day) == hanukkah_start_day:
+ if start_day <= today <= end_day:
+ if start_day == today:
now = datetime.datetime.utcnow()
hours = now.hour + 4 # using only hours
hanukkah_start_hour = 18
@@ -77,35 +73,27 @@ class HanukkahEmbed(commands.Cog):
)
await ctx.send(embed=embed)
return
- festival_day = self.hanukkah_days.index(day)
+ festival_day = hanukkah_dates.index(today)
number_suffixes = ["st", "nd", "rd", "th"]
suffix = number_suffixes[festival_day - 1 if festival_day <= 3 else 3]
message = ":menorah:" * festival_day
- embed.description = f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}"
- await ctx.send(embed=embed)
+ embed.description = (
+ f"It is the {festival_day}{suffix} day of Hanukkah!\n{message}"
+ )
+ elif today < start_day:
+ format_start = start_day.strftime("%d of %B")
+ embed.description = (
+ "Hanukkah has not started yet. "
+ f"Hanukkah will start at sundown on {format_start}."
+ )
else:
- if today < hanukkah_start:
- festival_starting_month = hanukkah_start.strftime("%B")
- embed.description = (
- f"Hanukkah has not started yet. "
- f"Hanukkah will start at sundown on {hanukkah_start_day}th "
- f"of {festival_starting_month}."
- )
- else:
- festival_end_month = hanukkah_end.strftime("%B")
- embed.description = (
- f"Looks like you missed Hanukkah!"
- f"Hanukkah ended on {hanukkah_end_day}th of {festival_end_month}."
- )
-
- await ctx.send(embed=embed)
+ format_end = end_day.strftime("%d of %B")
+ embed.description = (
+ "Looks like you missed Hanukkah! "
+ f"Hanukkah ended on {format_end}."
+ )
- def hanukkah_dates_split(self, hanukkah_dates: list[str]) -> None:
- """We are splitting the dates for hanukkah into days, months and years."""
- for date in hanukkah_dates:
- self.hanukkah_days.append(date[8:10])
- self.hanukkah_months.append(date[5:7])
- self.hanukkah_years.append(date[0:4])
+ await ctx.send(embed=embed)
def setup(bot: Bot) -> None:
diff --git a/bot/exts/holidays/pride/pride_facts.py b/bot/exts/holidays/pride/pride_facts.py
index e6ef7108..340f0b43 100644
--- a/bot/exts/holidays/pride/pride_facts.py
+++ b/bot/exts/holidays/pride/pride_facts.py
@@ -30,7 +30,7 @@ class PrideFacts(commands.Cog):
"""Background task to post the daily pride fact every day."""
await self.bot.wait_until_guild_available()
- channel = self.bot.get_channel(Channels.community_bot_commands)
+ channel = self.bot.get_channel(Channels.sir_lancebot_playground)
await self.send_select_fact(channel, datetime.utcnow())
async def send_random_fact(self, ctx: commands.Context) -> None:
diff --git a/bot/exts/holidays/pride/pride_leader.py b/bot/exts/holidays/pride/pride_leader.py
index 298c9328..adf01134 100644
--- a/bot/exts/holidays/pride/pride_leader.py
+++ b/bot/exts/holidays/pride/pride_leader.py
@@ -83,7 +83,7 @@ class PrideLeader(commands.Cog):
embed.add_field(
name="For More Information",
value=f"Do `{constants.Client.prefix}wiki {name}`"
- f" in <#{constants.Channels.community_bot_commands}>",
+ f" in <#{constants.Channels.sir_lancebot_playground}>",
inline=False
)
embed.set_thumbnail(url=pride_leader["url"])
diff --git a/bot/exts/holidays/valentines/be_my_valentine.py b/bot/exts/holidays/valentines/be_my_valentine.py
index 4d454c3a..cbb95157 100644
--- a/bot/exts/holidays/valentines/be_my_valentine.py
+++ b/bot/exts/holidays/valentines/be_my_valentine.py
@@ -7,14 +7,16 @@ import discord
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import Channels, Colours, Lovefest, Month
+from bot.constants import Channels, Colours, Lovefest, Month, PYTHON_PREFIX
from bot.utils.decorators import in_month
-from bot.utils.extensions import invoke_help_command
+from bot.utils.exceptions import MovedCommandError
log = logging.getLogger(__name__)
HEART_EMOJIS = [":heart:", ":gift_heart:", ":revolving_hearts:", ":sparkling_heart:", ":two_hearts:"]
+MOVED_COMMAND = f"{PYTHON_PREFIX}subscribe"
+
class BeMyValentine(commands.Cog):
"""A cog that sends Valentines to other users!"""
@@ -30,40 +32,14 @@ class BeMyValentine(commands.Cog):
return loads(p.read_text("utf8"))
@in_month(Month.FEBRUARY)
- @commands.group(name="lovefest")
+ @commands.command(name="lovefest", help=f"NOTE: This command has been moved to {MOVED_COMMAND}")
async def lovefest_role(self, ctx: commands.Context) -> None:
"""
- Subscribe or unsubscribe from the lovefest role.
-
- The lovefest role makes you eligible to receive anonymous valentines from other users.
+ Deprecated lovefest role command.
- 1) use the command \".lovefest sub\" to get the lovefest role.
- 2) use the command \".lovefest unsub\" to get rid of the lovefest role.
+ This command has been moved to bot, and will be removed in the future.
"""
- if not ctx.invoked_subcommand:
- await invoke_help_command(ctx)
-
- @lovefest_role.command(name="sub")
- async def add_role(self, ctx: commands.Context) -> None:
- """Adds the lovefest role."""
- user = ctx.author
- role = ctx.guild.get_role(Lovefest.role_id)
- if role not in ctx.author.roles:
- await user.add_roles(role)
- await ctx.send("The Lovefest role has been added !")
- else:
- await ctx.send("You already have the role !")
-
- @lovefest_role.command(name="unsub")
- async def remove_role(self, ctx: commands.Context) -> None:
- """Removes the lovefest role."""
- user = ctx.author
- role = ctx.guild.get_role(Lovefest.role_id)
- if role not in ctx.author.roles:
- await ctx.send("You dont have the lovefest role.")
- else:
- await user.remove_roles(role)
- await ctx.send("The lovefest role has been successfully removed!")
+ raise MovedCommandError(MOVED_COMMAND)
@commands.cooldown(1, 1800, commands.BucketType.user)
@commands.group(name="bemyvalentine", invoke_without_command=True)
@@ -94,7 +70,7 @@ class BeMyValentine(commands.Cog):
raise commands.UserInputError("Come on, you can't send a valentine to yourself :expressionless:")
emoji_1, emoji_2 = self.random_emoji()
- channel = self.bot.get_channel(Channels.community_bot_commands)
+ channel = self.bot.get_channel(Channels.sir_lancebot_playground)
valentine, title = self.valentine_check(valentine_type)
embed = discord.Embed(
diff --git a/bot/exts/holidays/valentines/lovecalculator.py b/bot/exts/holidays/valentines/lovecalculator.py
index 3999db2b..10dea9df 100644
--- a/bot/exts/holidays/valentines/lovecalculator.py
+++ b/bot/exts/holidays/valentines/lovecalculator.py
@@ -12,7 +12,7 @@ from discord.ext import commands
from discord.ext.commands import BadArgument, Cog, clean_content
from bot.bot import Bot
-from bot.constants import Channels, Client, Lovefest, Month
+from bot.constants import Channels, Lovefest, Month, PYTHON_PREFIX
from bot.utils.decorators import in_month
log = logging.getLogger(__name__)
@@ -32,7 +32,7 @@ class LoveCalculator(Cog):
Tells you how much the two love each other.
This command requires at least one member as input, if two are given love will be calculated between
- those two users, if only one is given, the second member is asusmed to be the invoker.
+ those two users, if only one is given, the second member is assumed to be the invoker.
Members are converted from:
- User ID
- Mention
@@ -51,7 +51,7 @@ class LoveCalculator(Cog):
raise BadArgument(
"This command can only be ran against members with the lovefest role! "
"This role be can assigned by running "
- f"`{Client.prefix}lovefest sub` in <#{Channels.community_bot_commands}>."
+ f"`{PYTHON_PREFIX}subscribe` in <#{Channels.bot_commands}>."
)
if whom is None:
@@ -74,7 +74,8 @@ class LoveCalculator(Cog):
# We need the -1 due to how bisect returns the point
# see the documentation for further detail
# https://docs.python.org/3/library/bisect.html#bisect.bisect
- index = bisect.bisect(LOVE_DATA, (love_percent,)) - 1
+ love_threshold = [threshold for threshold, _ in LOVE_DATA]
+ index = bisect.bisect(love_threshold, love_percent) - 1
# We already have the nearest "fit" love level
# We only need the dict, so we can ditch the first element
_, data = LOVE_DATA[index]
@@ -89,7 +90,7 @@ class LoveCalculator(Cog):
name="A letter from Dr. Love:",
value=data["text"]
)
- embed.set_footer(text=f"You can unsubscribe from lovefest by using {Client.prefix}lovefest unsub")
+ embed.set_footer(text=f"You can unsubscribe from lovefest by using {PYTHON_PREFIX}subscribe.")
await ctx.send(embed=embed)
diff --git a/bot/exts/utilities/bookmark.py b/bot/exts/utilities/bookmark.py
index a91ef1c0..b50205a0 100644
--- a/bot/exts/utilities/bookmark.py
+++ b/bot/exts/utilities/bookmark.py
@@ -7,7 +7,7 @@ import discord
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import Categories, Colours, ERROR_REPLIES, Icons, WHITELISTED_CHANNELS
+from bot.constants import Colours, ERROR_REPLIES, Icons, Roles
from bot.utils.converters import WrappedMessageConverter
from bot.utils.decorators import whitelist_override
@@ -16,7 +16,6 @@ log = logging.getLogger(__name__)
# Number of seconds to wait for other users to bookmark the same message
TIMEOUT = 120
BOOKMARK_EMOJI = "📌"
-WHITELISTED_CATEGORIES = (Categories.help_in_use,)
class Bookmark(commands.Cog):
@@ -87,8 +86,8 @@ class Bookmark(commands.Cog):
await message.add_reaction(BOOKMARK_EMOJI)
return message
- @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES)
@commands.command(name="bookmark", aliases=("bm", "pin"))
+ @whitelist_override(roles=(Roles.everyone,))
async def bookmark(
self,
ctx: commands.Context,
@@ -99,7 +98,13 @@ class Bookmark(commands.Cog):
"""Send the author a link to `target_message` via DMs."""
if not target_message:
if not ctx.message.reference:
- raise commands.UserInputError("You must either provide a valid message to bookmark, or reply to one.")
+ raise commands.UserInputError(
+ "You must either provide a valid message to bookmark, or reply to one."
+ "\n\nThe lookup strategy for a message is as follows (in order):"
+ "\n1. Lookup by '{channel ID}-{message ID}' (retrieved by shift-clicking on 'Copy ID')"
+ "\n2. Lookup by message ID (the message **must** be in the context channel)"
+ "\n3. Lookup by message URL"
+ )
target_message = ctx.message.reference.resolved
# Prevent users from bookmarking a message in a channel they don't have access to
diff --git a/bot/exts/utilities/challenges.py b/bot/exts/utilities/challenges.py
new file mode 100644
index 00000000..ab7ae442
--- /dev/null
+++ b/bot/exts/utilities/challenges.py
@@ -0,0 +1,341 @@
+import logging
+from asyncio import to_thread
+from random import choice
+from typing import Union
+
+from bs4 import BeautifulSoup
+from discord import Embed, Interaction, SelectOption, ui
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.constants import Colours, Emojis, NEGATIVE_REPLIES
+
+log = logging.getLogger(__name__)
+API_ROOT = "https://www.codewars.com/api/v1/code-challenges/{kata_id}"
+
+# Map difficulty for the kata to color we want to display in the embed.
+# These colors are representative of the colors that each kyu's level represents on codewars.com
+MAPPING_OF_KYU = {
+ 8: 0xdddbda, 7: 0xdddbda, 6: 0xecb613, 5: 0xecb613,
+ 4: 0x3c7ebb, 3: 0x3c7ebb, 2: 0x866cc7, 1: 0x866cc7
+}
+
+# Supported languages for a kata on codewars.com
+SUPPORTED_LANGUAGES = {
+ "stable": [
+ "c", "c#", "c++", "clojure", "coffeescript", "coq", "crystal", "dart", "elixir",
+ "f#", "go", "groovy", "haskell", "java", "javascript", "kotlin", "lean", "lua", "nasm",
+ "php", "python", "racket", "ruby", "rust", "scala", "shell", "sql", "swift", "typescript"
+ ],
+ "beta": [
+ "agda", "bf", "cfml", "cobol", "commonlisp", "elm", "erlang", "factor",
+ "forth", "fortran", "haxe", "idris", "julia", "nim", "objective-c", "ocaml",
+ "pascal", "perl", "powershell", "prolog", "purescript", "r", "raku", "reason", "solidity", "vb.net"
+ ]
+}
+
+
+class InformationDropdown(ui.Select):
+ """A dropdown inheriting from ui.Select that allows finding out other information about the kata."""
+
+ def __init__(self, language_embed: Embed, tags_embed: Embed, other_info_embed: Embed, main_embed: Embed):
+ options = [
+ SelectOption(
+ label="Main Information",
+ description="See the kata's difficulty, description, etc.",
+ emoji="🌎"
+ ),
+ SelectOption(
+ label="Languages",
+ description="See what languages this kata supports!",
+ emoji=Emojis.reddit_post_text
+ ),
+ SelectOption(
+ label="Tags",
+ description="See what categories this kata falls under!",
+ emoji=Emojis.stackoverflow_tag
+ ),
+ SelectOption(
+ label="Other Information",
+ description="See how other people performed on this kata and more!",
+ emoji="ℹ"
+ )
+ ]
+
+ # We map the option label to the embed instance so that it can be easily looked up later in O(1)
+ self.mapping_of_embeds = {
+ "Main Information": main_embed,
+ "Languages": language_embed,
+ "Tags": tags_embed,
+ "Other Information": other_info_embed,
+ }
+
+ super().__init__(
+ placeholder="See more information regarding this kata",
+ min_values=1,
+ max_values=1,
+ options=options
+ )
+
+ async def callback(self, interaction: Interaction) -> None:
+ """Callback for when someone clicks on a dropdown."""
+ # Edit the message to the embed selected in the option
+ # The `original_message` attribute is set just after the message is sent with the view.
+ # The attribute is not set during initialization.
+ result_embed = self.mapping_of_embeds[self.values[0]]
+ await self.original_message.edit(embed=result_embed)
+
+
+class Challenges(commands.Cog):
+ """
+ Cog for the challenge command.
+
+ The challenge command pulls a random kata from codewars.com.
+ A kata is the name for a challenge, specific to codewars.com.
+
+ The challenge command also has filters to customize the kata that is given.
+ You can specify the language the kata should be from, difficulty and topic of the kata.
+ """
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ async def kata_id(self, search_link: str, params: dict) -> Union[str, Embed]:
+ """
+ Uses bs4 to get the HTML code for the page of katas, where the page is the link of the formatted `search_link`.
+
+ This will webscrape the search page with `search_link` and then get the ID of a kata for the
+ codewars.com API to use.
+ """
+ async with self.bot.http_session.get(search_link, params=params) as response:
+ if response.status != 200:
+ error_embed = Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="We ran into an error when getting the kata from codewars.com, try again later.",
+ color=Colours.soft_red
+ )
+ log.error(f"Unexpected response from codewars.com, status code: {response.status}")
+ return error_embed
+
+ soup = BeautifulSoup(await response.text(), features="lxml")
+ first_kata_div = await to_thread(soup.find_all, "div", class_="item-title px-0")
+
+ if not first_kata_div:
+ raise commands.BadArgument("No katas could be found with the filters provided.")
+ elif len(first_kata_div) >= 3:
+ first_kata_div = choice(first_kata_div[:3])
+ elif "q=" not in search_link:
+ first_kata_div = choice(first_kata_div)
+ else:
+ first_kata_div = first_kata_div[0]
+
+ # There are numerous divs before arriving at the id of the kata, which can be used for the link.
+ first_kata_id = first_kata_div.a["href"].split("/")[-1]
+ return first_kata_id
+
+ async def kata_information(self, kata_id: str) -> Union[dict, Embed]:
+ """
+ Returns the information about the Kata.
+
+ Uses the codewars.com API to get information about the kata using `kata_id`.
+ """
+ async with self.bot.http_session.get(API_ROOT.format(kata_id=kata_id)) as response:
+ if response.status != 200:
+ error_embed = Embed(
+ title=choice(NEGATIVE_REPLIES),
+ description="We ran into an error when getting the kata information, try again later.",
+ color=Colours.soft_red
+ )
+ log.error(f"Unexpected response from codewars.com/api/v1, status code: {response.status}")
+ return error_embed
+
+ return await response.json()
+
+ @staticmethod
+ def main_embed(kata_information: dict) -> Embed:
+ """Creates the main embed which displays the name, difficulty and description of the kata."""
+ kata_description = kata_information["description"]
+ kata_url = f"https://codewars.com/kata/{kata_information['id']}"
+
+ # Ensuring it isn't over the length 1024
+ if len(kata_description) > 1024:
+ kata_description = "\n".join(kata_description[:1000].split("\n")[:-1]) + "..."
+ kata_description += f" [continue reading]({kata_url})"
+
+ if kata_information["rank"]["name"] is None:
+ embed_color = 8
+ kata_difficulty = "Unable to retrieve difficulty for beta languages."
+ else:
+ embed_color = int(kata_information["rank"]["name"].replace(" kyu", ""))
+ kata_difficulty = kata_information["rank"]["name"]
+
+ kata_embed = Embed(
+ title=kata_information["name"],
+ description=kata_description,
+ color=MAPPING_OF_KYU[embed_color],
+ url=kata_url
+ )
+ kata_embed.add_field(name="Difficulty", value=kata_difficulty, inline=False)
+ return kata_embed
+
+ @staticmethod
+ def language_embed(kata_information: dict) -> Embed:
+ """Creates the 'language embed' which displays all languages the kata supports."""
+ kata_url = f"https://codewars.com/kata/{kata_information['id']}"
+
+ languages = "\n".join(map(str.title, kata_information["languages"]))
+ language_embed = Embed(
+ title=kata_information["name"],
+ description=f"```yaml\nSupported Languages:\n{languages}\n```",
+ color=Colours.python_blue,
+ url=kata_url
+ )
+ return language_embed
+
+ @staticmethod
+ def tags_embed(kata_information: dict) -> Embed:
+ """
+ Creates the 'tags embed' which displays all the tags of the Kata.
+
+ Tags explain what the kata is about, this is what codewars.com calls categories.
+ """
+ kata_url = f"https://codewars.com/kata/{kata_information['id']}"
+
+ tags = "\n".join(kata_information["tags"])
+ tags_embed = Embed(
+ title=kata_information["name"],
+ description=f"```yaml\nTags:\n{tags}\n```",
+ color=Colours.grass_green,
+ url=kata_url
+ )
+ return tags_embed
+
+ @staticmethod
+ def miscellaneous_embed(kata_information: dict) -> Embed:
+ """
+ Creates the 'other information embed' which displays miscellaneous information about the kata.
+
+ This embed shows statistics such as the total number of people who completed the kata,
+ the total number of stars of the kata, etc.
+ """
+ kata_url = f"https://codewars.com/kata/{kata_information['id']}"
+
+ embed = Embed(
+ title=kata_information["name"],
+ description="```nim\nOther Information\n```",
+ color=Colours.grass_green,
+ url=kata_url
+ )
+ embed.add_field(
+ name="`Total Score`",
+ value=f"```css\n{kata_information['voteScore']}\n```",
+ inline=False
+ )
+ embed.add_field(
+ name="`Total Stars`",
+ value=f"```css\n{kata_information['totalStars']}\n```",
+ inline=False
+ )
+ embed.add_field(
+ name="`Total Completed`",
+ value=f"```css\n{kata_information['totalCompleted']}\n```",
+ inline=False
+ )
+ embed.add_field(
+ name="`Total Attempts`",
+ value=f"```css\n{kata_information['totalAttempts']}\n```",
+ inline=False
+ )
+ return embed
+
+ @staticmethod
+ def create_view(dropdown: InformationDropdown, link: str) -> ui.View:
+ """
+ Creates the discord.py View for the Discord message components (dropdowns and buttons).
+
+ The discord UI is implemented onto the embed, where the user can choose what information about the kata they
+ want, along with a link button to the kata itself.
+ """
+ view = ui.View()
+ view.add_item(dropdown)
+ view.add_item(ui.Button(label="View the Kata", url=link))
+ return view
+
+ @commands.command(aliases=["kata"])
+ @commands.cooldown(1, 5, commands.BucketType.user)
+ async def challenge(self, ctx: commands.Context, language: str = "python", *, query: str = None) -> None:
+ """
+ The challenge command pulls a random kata (challenge) from codewars.com.
+
+ The different ways to use this command are:
+ `.challenge <language>` - Pulls a random challenge within that language's scope.
+ `.challenge <language> <difficulty>` - The difficulty can be from 1-8,
+ 1 being the hardest, 8 being the easiest. This pulls a random challenge within that difficulty & language.
+ `.challenge <language> <query>` - Pulls a random challenge with the query provided under the language
+ `.challenge <language> <query>, <difficulty>` - Pulls a random challenge with the query provided,
+ under that difficulty within the language's scope.
+ """
+ language = language.lower()
+ if language not in SUPPORTED_LANGUAGES["stable"] + SUPPORTED_LANGUAGES["beta"]:
+ raise commands.BadArgument("This is not a recognized language on codewars.com!")
+
+ get_kata_link = f"https://codewars.com/kata/search/{language}"
+ params = {}
+
+ if query is not None:
+ if "," in query:
+ query_splitted = query.split("," if ", " not in query else ", ")
+
+ if len(query_splitted) > 2:
+ raise commands.BadArgument(
+ "There can only be one comma within the query, separating the difficulty and the query itself."
+ )
+
+ query, level = query_splitted
+ params["q"] = query
+ params["r[]"] = f"-{level}"
+ elif query.isnumeric():
+ params["r[]"] = f"-{query}"
+ else:
+ params["q"] = query
+
+ params["beta"] = str(language in SUPPORTED_LANGUAGES["beta"]).lower()
+
+ first_kata_id = await self.kata_id(get_kata_link, params)
+ if isinstance(first_kata_id, Embed):
+ # We ran into an error when retrieving the website link
+ await ctx.send(embed=first_kata_id)
+ return
+
+ kata_information = await self.kata_information(first_kata_id)
+ if isinstance(kata_information, Embed):
+ # Something went wrong when trying to fetch the kata information
+ await ctx.d(embed=kata_information)
+ return
+
+ kata_embed = self.main_embed(kata_information)
+ language_embed = self.language_embed(kata_information)
+ tags_embed = self.tags_embed(kata_information)
+ miscellaneous_embed = self.miscellaneous_embed(kata_information)
+
+ dropdown = InformationDropdown(
+ main_embed=kata_embed,
+ language_embed=language_embed,
+ tags_embed=tags_embed,
+ other_info_embed=miscellaneous_embed
+ )
+ kata_view = self.create_view(dropdown, f"https://codewars.com/kata/{first_kata_id}")
+ original_message = await ctx.send(
+ embed=kata_embed,
+ view=kata_view
+ )
+ dropdown.original_message = original_message
+
+ wait_for_kata = await kata_view.wait()
+ if wait_for_kata:
+ await original_message.edit(embed=kata_embed, view=None)
+
+
+def setup(bot: Bot) -> None:
+ """Load the Challenges cog."""
+ bot.add_cog(Challenges(bot))
diff --git a/bot/exts/utilities/colour.py b/bot/exts/utilities/colour.py
new file mode 100644
index 00000000..ee6bad93
--- /dev/null
+++ b/bot/exts/utilities/colour.py
@@ -0,0 +1,266 @@
+import colorsys
+import json
+import pathlib
+import random
+import string
+from io import BytesIO
+from typing import Optional
+
+import discord
+import rapidfuzz
+from PIL import Image, ImageColor
+from discord.ext import commands
+
+from bot import constants
+from bot.bot import Bot
+from bot.exts.core.extensions import invoke_help_command
+from bot.utils.decorators import whitelist_override
+
+THUMBNAIL_SIZE = (80, 80)
+
+
+class Colour(commands.Cog):
+ """Cog for the Colour command."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+ with open(pathlib.Path("bot/resources/utilities/ryanzec_colours.json")) as f:
+ self.colour_mapping = json.load(f)
+ del self.colour_mapping['_'] # Delete source credit entry
+
+ async def send_colour_response(self, ctx: commands.Context, rgb: tuple[int, int, int]) -> None:
+ """Create and send embed from user given colour information."""
+ name = self._rgb_to_name(rgb)
+ try:
+ colour_or_color = ctx.invoked_parents[0]
+ except IndexError:
+ colour_or_color = "colour"
+
+ colour_mode = ctx.invoked_with
+ if colour_mode == "random":
+ colour_mode = colour_or_color
+ input_colour = name
+ elif colour_mode in ("colour", "color"):
+ input_colour = ctx.kwargs["colour_input"]
+ elif colour_mode == "name":
+ input_colour = ctx.kwargs["user_colour_name"]
+ elif colour_mode == "hex":
+ input_colour = ctx.args[2:][0]
+ if len(input_colour) > 7:
+ input_colour = input_colour[0:-2]
+ else:
+ input_colour = tuple(ctx.args[2:])
+
+ if colour_mode not in ("name", "hex", "random", "color", "colour"):
+ colour_mode = colour_mode.upper()
+ else:
+ colour_mode = colour_mode.title()
+
+ colour_embed = discord.Embed(
+ title=f"{name or input_colour}",
+ description=f"{colour_or_color.title()} information for {colour_mode} `{input_colour or name}`.",
+ colour=discord.Color.from_rgb(*rgb)
+ )
+ colour_conversions = self.get_colour_conversions(rgb)
+ for colour_space, value in colour_conversions.items():
+ colour_embed.add_field(
+ name=colour_space,
+ value=f"`{value}`",
+ inline=True
+ )
+
+ thumbnail = Image.new("RGB", THUMBNAIL_SIZE, color=rgb)
+ buffer = BytesIO()
+ thumbnail.save(buffer, "PNG")
+ buffer.seek(0)
+ thumbnail_file = discord.File(buffer, filename="colour.png")
+
+ colour_embed.set_thumbnail(url="attachment://colour.png")
+
+ await ctx.send(file=thumbnail_file, embed=colour_embed)
+
+ @commands.group(aliases=("color",), invoke_without_command=True)
+ @whitelist_override(
+ channels=constants.WHITELISTED_CHANNELS,
+ roles=constants.STAFF_ROLES,
+ categories=[constants.Categories.development, constants.Categories.media]
+ )
+ async def colour(self, ctx: commands.Context, *, colour_input: Optional[str] = None) -> None:
+ """
+ Create an embed that displays colour information.
+
+ If no subcommand is called, a randomly selected colour will be shown.
+ """
+ if colour_input is None:
+ await self.random(ctx)
+ return
+
+ try:
+ extra_colour = ImageColor.getrgb(colour_input)
+ await self.send_colour_response(ctx, extra_colour)
+ except ValueError:
+ await invoke_help_command(ctx)
+
+ @colour.command()
+ async def rgb(self, ctx: commands.Context, red: int, green: int, blue: int) -> None:
+ """Create an embed from an RGB input."""
+ if any(c not in range(256) for c in (red, green, blue)):
+ raise commands.BadArgument(
+ message=f"RGB values can only be from 0 to 255. User input was: `{red, green, blue}`."
+ )
+ rgb_tuple = (red, green, blue)
+ await self.send_colour_response(ctx, rgb_tuple)
+
+ @colour.command()
+ async def hsv(self, ctx: commands.Context, hue: int, saturation: int, value: int) -> None:
+ """Create an embed from an HSV input."""
+ if (hue not in range(361)) or any(c not in range(101) for c in (saturation, value)):
+ raise commands.BadArgument(
+ message="Hue can only be from 0 to 360. Saturation and Value can only be from 0 to 100. "
+ f"User input was: `{hue, saturation, value}`."
+ )
+ hsv_tuple = ImageColor.getrgb(f"hsv({hue}, {saturation}%, {value}%)")
+ await self.send_colour_response(ctx, hsv_tuple)
+
+ @colour.command()
+ async def hsl(self, ctx: commands.Context, hue: int, saturation: int, lightness: int) -> None:
+ """Create an embed from an HSL input."""
+ if (hue not in range(361)) or any(c not in range(101) for c in (saturation, lightness)):
+ raise commands.BadArgument(
+ message="Hue can only be from 0 to 360. Saturation and Lightness can only be from 0 to 100. "
+ f"User input was: `{hue, saturation, lightness}`."
+ )
+ hsl_tuple = ImageColor.getrgb(f"hsl({hue}, {saturation}%, {lightness}%)")
+ await self.send_colour_response(ctx, hsl_tuple)
+
+ @colour.command()
+ async def cmyk(self, ctx: commands.Context, cyan: int, magenta: int, yellow: int, key: int) -> None:
+ """Create an embed from a CMYK input."""
+ if any(c not in range(101) for c in (cyan, magenta, yellow, key)):
+ raise commands.BadArgument(
+ message=f"CMYK values can only be from 0 to 100. User input was: `{cyan, magenta, yellow, key}`."
+ )
+ r = round(255 * (1 - (cyan / 100)) * (1 - (key / 100)))
+ g = round(255 * (1 - (magenta / 100)) * (1 - (key / 100)))
+ b = round(255 * (1 - (yellow / 100)) * (1 - (key / 100)))
+ await self.send_colour_response(ctx, (r, g, b))
+
+ @colour.command()
+ async def hex(self, ctx: commands.Context, hex_code: str) -> None:
+ """Create an embed from a HEX input."""
+ if hex_code[0] != "#":
+ hex_code = f"#{hex_code}"
+
+ if len(hex_code) not in (4, 5, 7, 9) or any(digit not in string.hexdigits for digit in hex_code[1:]):
+ raise commands.BadArgument(
+ message=f"Cannot convert `{hex_code}` to a recognizable Hex format. "
+ "Hex values must be hexadecimal and take the form *#RRGGBB* or *#RGB*."
+ )
+
+ hex_tuple = ImageColor.getrgb(hex_code)
+ if len(hex_tuple) == 4:
+ hex_tuple = hex_tuple[:-1] # Colour must be RGB. If RGBA, we remove the alpha value
+ await self.send_colour_response(ctx, hex_tuple)
+
+ @colour.command()
+ async def name(self, ctx: commands.Context, *, user_colour_name: str) -> None:
+ """Create an embed from a name input."""
+ hex_colour = self.match_colour_name(ctx, user_colour_name)
+ if hex_colour is None:
+ name_error_embed = discord.Embed(
+ title="No colour match found.",
+ description=f"No colour found for: `{user_colour_name}`",
+ colour=discord.Color.dark_red()
+ )
+ await ctx.send(embed=name_error_embed)
+ return
+ hex_tuple = ImageColor.getrgb(hex_colour)
+ await self.send_colour_response(ctx, hex_tuple)
+
+ @colour.command()
+ async def random(self, ctx: commands.Context) -> None:
+ """Create an embed from a randomly chosen colour."""
+ hex_colour = random.choice(list(self.colour_mapping.values()))
+ hex_tuple = ImageColor.getrgb(f"#{hex_colour}")
+ await self.send_colour_response(ctx, hex_tuple)
+
+ def get_colour_conversions(self, rgb: tuple[int, int, int]) -> dict[str, str]:
+ """Create a dictionary mapping of colour types and their values."""
+ colour_name = self._rgb_to_name(rgb)
+ if colour_name is None:
+ colour_name = "No match found"
+ return {
+ "RGB": rgb,
+ "HSV": self._rgb_to_hsv(rgb),
+ "HSL": self._rgb_to_hsl(rgb),
+ "CMYK": self._rgb_to_cmyk(rgb),
+ "Hex": self._rgb_to_hex(rgb),
+ "Name": colour_name
+ }
+
+ @staticmethod
+ def _rgb_to_hsv(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
+ """Convert RGB values to HSV values."""
+ rgb_list = [val / 255 for val in rgb]
+ h, s, v = colorsys.rgb_to_hsv(*rgb_list)
+ hsv = (round(h * 360), round(s * 100), round(v * 100))
+ return hsv
+
+ @staticmethod
+ def _rgb_to_hsl(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
+ """Convert RGB values to HSL values."""
+ rgb_list = [val / 255.0 for val in rgb]
+ h, l, s = colorsys.rgb_to_hls(*rgb_list)
+ hsl = (round(h * 360), round(s * 100), round(l * 100))
+ return hsl
+
+ @staticmethod
+ def _rgb_to_cmyk(rgb: tuple[int, int, int]) -> tuple[int, int, int, int]:
+ """Convert RGB values to CMYK values."""
+ rgb_list = [val / 255.0 for val in rgb]
+ if not any(rgb_list):
+ return 0, 0, 0, 100
+ k = 1 - max(rgb_list)
+ c = round((1 - rgb_list[0] - k) * 100 / (1 - k))
+ m = round((1 - rgb_list[1] - k) * 100 / (1 - k))
+ y = round((1 - rgb_list[2] - k) * 100 / (1 - k))
+ cmyk = (c, m, y, round(k * 100))
+ return cmyk
+
+ @staticmethod
+ def _rgb_to_hex(rgb: tuple[int, int, int]) -> str:
+ """Convert RGB values to HEX code."""
+ hex_ = "".join([hex(val)[2:].zfill(2) for val in rgb])
+ hex_code = f"#{hex_}".upper()
+ return hex_code
+
+ def _rgb_to_name(self, rgb: tuple[int, int, int]) -> Optional[str]:
+ """Convert RGB values to a fuzzy matched name."""
+ input_hex_colour = self._rgb_to_hex(rgb)
+ try:
+ match, certainty, _ = rapidfuzz.process.extractOne(
+ query=input_hex_colour,
+ choices=self.colour_mapping.values(),
+ score_cutoff=80
+ )
+ colour_name = [name for name, hex_code in self.colour_mapping.items() if hex_code == match][0]
+ except TypeError:
+ colour_name = None
+ return colour_name
+
+ def match_colour_name(self, ctx: commands.Context, input_colour_name: str) -> Optional[str]:
+ """Convert a colour name to HEX code."""
+ try:
+ match, certainty, _ = rapidfuzz.process.extractOne(
+ query=input_colour_name,
+ choices=self.colour_mapping.keys(),
+ score_cutoff=80
+ )
+ except (ValueError, TypeError):
+ return
+ return f"#{self.colour_mapping[match]}"
+
+
+def setup(bot: Bot) -> None:
+ """Load the Colour cog."""
+ bot.add_cog(Colour(bot))
diff --git a/bot/exts/utilities/conversationstarters.py b/bot/exts/utilities/conversationstarters.py
index dd537022..8bf2abfd 100644
--- a/bot/exts/utilities/conversationstarters.py
+++ b/bot/exts/utilities/conversationstarters.py
@@ -1,11 +1,15 @@
+import asyncio
+from contextlib import suppress
+from functools import partial
from pathlib import Path
+from typing import Union
+import discord
import yaml
-from discord import Color, Embed
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import WHITELISTED_CHANNELS
+from bot.constants import MODERATION_ROLES, WHITELISTED_CHANNELS
from bot.utils.decorators import whitelist_override
from bot.utils.randomization import RandomCycle
@@ -35,35 +39,88 @@ TOPICS = {
class ConvoStarters(commands.Cog):
"""General conversation topics."""
- @commands.command()
- @whitelist_override(channels=ALL_ALLOWED_CHANNELS)
- async def topic(self, ctx: commands.Context) -> None:
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ @staticmethod
+ def _build_topic_embed(channel_id: int) -> discord.Embed:
"""
- Responds with a random topic to start a conversation.
+ Build an embed containing a conversation topic.
If in a Python channel, a python-related topic will be given.
-
Otherwise, a random conversation topic will be received by the user.
"""
# No matter what, the form will be shown.
- embed = Embed(description=f"Suggest more topics [here]({SUGGESTION_FORM})!", color=Color.blurple())
+ embed = discord.Embed(
+ description=f"Suggest more topics [here]({SUGGESTION_FORM})!",
+ color=discord.Colour.og_blurple()
+ )
try:
- # Fetching topics.
- channel_topics = TOPICS[ctx.channel.id]
-
- # If the channel isn't Python-related.
+ channel_topics = TOPICS[channel_id]
except KeyError:
+ # Channel doesn't have any topics.
embed.title = f"**{next(TOPICS['default'])}**"
-
- # If the channel ID doesn't have any topics.
else:
embed.title = f"**{next(channel_topics)}**"
+ return embed
+
+ @staticmethod
+ def _predicate(
+ command_invoker: Union[discord.User, discord.Member],
+ message: discord.Message,
+ reaction: discord.Reaction,
+ user: discord.User
+ ) -> bool:
+ user_is_moderator = any(role.id in MODERATION_ROLES for role in getattr(user, "roles", []))
+ user_is_invoker = user.id == command_invoker.id
+
+ is_right_reaction = all((
+ reaction.message.id == message.id,
+ str(reaction.emoji) == "🔄",
+ user_is_moderator or user_is_invoker
+ ))
+ return is_right_reaction
+
+ async def _listen_for_refresh(
+ self,
+ command_invoker: Union[discord.User, discord.Member],
+ message: discord.Message
+ ) -> None:
+ await message.add_reaction("🔄")
+ while True:
+ try:
+ reaction, user = await self.bot.wait_for(
+ "reaction_add",
+ check=partial(self._predicate, command_invoker, message),
+ timeout=60.0
+ )
+ except asyncio.TimeoutError:
+ with suppress(discord.NotFound):
+ await message.clear_reaction("🔄")
+ break
+
+ try:
+ await message.edit(embed=self._build_topic_embed(message.channel.id))
+ except discord.NotFound:
+ break
+
+ with suppress(discord.NotFound):
+ await message.remove_reaction(reaction, user)
- finally:
- await ctx.send(embed=embed)
+ @commands.command()
+ @commands.cooldown(1, 60*2, commands.BucketType.channel)
+ @whitelist_override(channels=ALL_ALLOWED_CHANNELS)
+ async def topic(self, ctx: commands.Context) -> None:
+ """
+ Responds with a random topic to start a conversation.
+
+ Allows the refresh of a topic by pressing an emoji.
+ """
+ message = await ctx.send(embed=self._build_topic_embed(ctx.channel.id))
+ self.bot.loop.create_task(self._listen_for_refresh(ctx.author, message))
def setup(bot: Bot) -> None:
"""Load the ConvoStarters cog."""
- bot.add_cog(ConvoStarters())
+ bot.add_cog(ConvoStarters(bot))
diff --git a/bot/exts/utilities/emoji.py b/bot/exts/utilities/emoji.py
index 55d6b8e9..fa438d7f 100644
--- a/bot/exts/utilities/emoji.py
+++ b/bot/exts/utilities/emoji.py
@@ -107,11 +107,11 @@ class Emojis(commands.Cog):
title=f"Emoji Information: {emoji.name}",
description=textwrap.dedent(f"""
**Name:** {emoji.name}
- **Created:** {time_since(emoji.created_at, precision="hours")}
- **Date:** {datetime.strftime(emoji.created_at, "%d/%m/%Y")}
+ **Created:** {time_since(emoji.created_at.replace(tzinfo=None), precision="hours")}
+ **Date:** {datetime.strftime(emoji.created_at.replace(tzinfo=None), "%d/%m/%Y")}
**ID:** {emoji.id}
"""),
- color=Color.blurple(),
+ color=Color.og_blurple(),
url=str(emoji.url),
).set_thumbnail(url=emoji.url)
diff --git a/bot/exts/utilities/epoch.py b/bot/exts/utilities/epoch.py
new file mode 100644
index 00000000..42312dd1
--- /dev/null
+++ b/bot/exts/utilities/epoch.py
@@ -0,0 +1,138 @@
+from typing import Optional, Union
+
+import arrow
+import discord
+from dateutil import parser
+from discord.ext import commands
+
+from bot.bot import Bot
+from bot.utils.extensions import invoke_help_command
+
+# https://discord.com/developers/docs/reference#message-formatting-timestamp-styles
+STYLES = {
+ "Epoch": ("",),
+ "Short Time": ("t", "h:mm A",),
+ "Long Time": ("T", "h:mm:ss A"),
+ "Short Date": ("d", "MM/DD/YYYY"),
+ "Long Date": ("D", "MMMM D, YYYY"),
+ "Short Date/Time": ("f", "MMMM D, YYYY h:mm A"),
+ "Long Date/Time": ("F", "dddd, MMMM D, YYYY h:mm A"),
+ "Relative Time": ("R",)
+}
+DROPDOWN_TIMEOUT = 60
+
+
+class DateString(commands.Converter):
+ """Convert a relative or absolute date/time string to an arrow.Arrow object."""
+
+ async def convert(self, ctx: commands.Context, argument: str) -> Union[arrow.Arrow, Optional[tuple]]:
+ """
+ Convert a relative or absolute date/time string to an arrow.Arrow object.
+
+ Try to interpret the date string as a relative time. If conversion fails, try to interpret it as an absolute
+ time. Tokens that are not recognised are returned along with the part of the string that was successfully
+ converted to an arrow object. If the date string cannot be parsed, BadArgument is raised.
+ """
+ try:
+ return arrow.utcnow().dehumanize(argument)
+ except (ValueError, OverflowError):
+ try:
+ dt, ignored_tokens = parser.parse(argument, fuzzy_with_tokens=True)
+ except parser.ParserError:
+ raise commands.BadArgument(f"`{argument}` Could not be parsed to a relative or absolute date.")
+ except OverflowError:
+ raise commands.BadArgument(f"`{argument}` Results in a date outside of the supported range.")
+ return arrow.get(dt), ignored_tokens
+
+
+class Epoch(commands.Cog):
+ """Convert an entered time and date to a unix timestamp."""
+
+ @commands.command(name="epoch")
+ async def epoch(self, ctx: commands.Context, *, date_time: DateString = None) -> None:
+ """
+ Convert an entered date/time string to the equivalent epoch.
+
+ **Relative time**
+ Must begin with `in...` or end with `...ago`.
+ Accepted units: "seconds", "minutes", "hours", "days", "weeks", "months", "years".
+ eg `.epoch in a month 4 days and 2 hours`
+
+ **Absolute time**
+ eg `.epoch 2022/6/15 16:43 -04:00`
+ Absolute times must be entered in descending orders of magnitude.
+ If AM or PM is left unspecified, the 24-hour clock is assumed.
+ Timezones are optional, and will default to UTC. The following timezone formats are accepted:
+ Z (UTC)
+ ±HH:MM
+ ±HHMM
+ ±HH
+
+ Times in the dropdown are shown in UTC
+ """
+ if not date_time:
+ await invoke_help_command(ctx)
+ return
+
+ if isinstance(date_time, tuple):
+ # Remove empty strings. Strip extra whitespace from the remaining items
+ ignored_tokens = list(map(str.strip, filter(str.strip, date_time[1])))
+ date_time = date_time[0]
+ if ignored_tokens:
+ await ctx.send(f"Could not parse the following token(s): `{', '.join(ignored_tokens)}`")
+ await ctx.send(f"Date and time parsed as: `{date_time.format(arrow.FORMAT_RSS)}`")
+
+ epoch = int(date_time.timestamp())
+ view = TimestampMenuView(ctx, self._format_dates(date_time), epoch)
+ original = await ctx.send(f"`{epoch}`", view=view)
+ await view.wait() # wait until expiration before removing the dropdown
+ try:
+ await original.edit(view=None)
+ except discord.NotFound: # disregard the error message if the message is deleled
+ pass
+
+ @staticmethod
+ def _format_dates(date: arrow.Arrow) -> list[str]:
+ """
+ Return a list of date strings formatted according to the discord timestamp styles.
+
+ These are used in the description of each style in the dropdown
+ """
+ date = date.to('utc')
+ formatted = [str(int(date.timestamp()))]
+ formatted += [date.format(format[1]) for format in list(STYLES.values())[1:7]]
+ formatted.append(date.humanize())
+ return formatted
+
+
+class TimestampMenuView(discord.ui.View):
+ """View for the epoch command which contains a single `discord.ui.Select` dropdown component."""
+
+ def __init__(self, ctx: commands.Context, formatted_times: list[str], epoch: int):
+ super().__init__(timeout=DROPDOWN_TIMEOUT)
+ self.ctx = ctx
+ self.epoch = epoch
+ self.dropdown: discord.ui.Select = self.children[0]
+ for label, date_time in zip(STYLES.keys(), formatted_times):
+ self.dropdown.add_option(label=label, description=date_time)
+
+ @discord.ui.select(placeholder="Select the format of your timestamp")
+ async def select_format(self, _: discord.ui.Select, interaction: discord.Interaction) -> discord.Message:
+ """Drop down menu which contains a list of formats which discord timestamps can take."""
+ selected = interaction.data["values"][0]
+ if selected == "Epoch":
+ return await interaction.response.edit_message(content=f"`{self.epoch}`")
+ return await interaction.response.edit_message(content=f"`<t:{self.epoch}:{STYLES[selected][0]}>`")
+
+ async def interaction_check(self, interaction: discord.Interaction) -> bool:
+ """Check to ensure that the interacting user is the user who invoked the command."""
+ if interaction.user != self.ctx.author:
+ embed = discord.Embed(description="Sorry, but this dropdown menu can only be used by the original author.")
+ await interaction.response.send_message(embed=embed, ephemeral=True)
+ return False
+ return True
+
+
+def setup(bot: Bot) -> None:
+ """Load the Epoch cog."""
+ bot.add_cog(Epoch())
diff --git a/bot/exts/utilities/githubinfo.py b/bot/exts/utilities/githubinfo.py
index d00b408d..963f54e5 100644
--- a/bot/exts/utilities/githubinfo.py
+++ b/bot/exts/utilities/githubinfo.py
@@ -1,30 +1,165 @@
import logging
import random
+import re
+import typing as t
+from dataclasses import dataclass
from datetime import datetime
-from urllib.parse import quote, quote_plus
+from urllib.parse import quote
import discord
+from aiohttp import ClientResponse
from discord.ext import commands
from bot.bot import Bot
-from bot.constants import Colours, NEGATIVE_REPLIES
+from bot.constants import Colours, ERROR_REPLIES, Emojis, NEGATIVE_REPLIES, Tokens
from bot.exts.core.extensions import invoke_help_command
log = logging.getLogger(__name__)
GITHUB_API_URL = "https://api.github.com"
+REQUEST_HEADERS = {
+ "Accept": "application/vnd.github.v3+json"
+}
+
+REPOSITORY_ENDPOINT = "https://api.github.com/orgs/{org}/repos?per_page=100&type=public"
+ISSUE_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/issues/{number}"
+PR_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/pulls/{number}"
+
+if Tokens.github:
+ REQUEST_HEADERS["Authorization"] = f"token {Tokens.github}"
+
+CODE_BLOCK_RE = re.compile(
+ r"^`([^`\n]+)`" # Inline codeblock
+ r"|```(.+?)```", # Multiline codeblock
+ re.DOTALL | re.MULTILINE
+)
+
+# Maximum number of issues in one message
+MAXIMUM_ISSUES = 5
+
+# Regex used when looking for automatic linking in messages
+# regex101 of current regex https://regex101.com/r/V2ji8M/6
+AUTOMATIC_REGEX = re.compile(
+ r"((?P<org>[a-zA-Z0-9][a-zA-Z0-9\-]{1,39})\/)?(?P<repo>[\w\-\.]{1,100})#(?P<number>[0-9]+)"
+)
+
+
+@dataclass(eq=True, frozen=True)
+class FoundIssue:
+ """Dataclass representing an issue found by the regex."""
+
+ organisation: t.Optional[str]
+ repository: str
+ number: str
+
+
+@dataclass(eq=True, frozen=True)
+class FetchError:
+ """Dataclass representing an error while fetching an issue."""
+
+ return_code: int
+ message: str
+
+
+@dataclass(eq=True, frozen=True)
+class IssueState:
+ """Dataclass representing the state of an issue."""
+
+ repository: str
+ number: int
+ url: str
+ title: str
+ emoji: str
+
class GithubInfo(commands.Cog):
- """Fetches info from GitHub."""
+ """A Cog that fetches info from GitHub."""
def __init__(self, bot: Bot):
self.bot = bot
+ self.repos = []
+
+ @staticmethod
+ def remove_codeblocks(message: str) -> str:
+ """Remove any codeblock in a message."""
+ return CODE_BLOCK_RE.sub("", message)
+
+ async def fetch_issue(
+ self,
+ number: int,
+ repository: str,
+ user: str
+ ) -> t.Union[IssueState, FetchError]:
+ """
+ Retrieve an issue from a GitHub repository.
+
+ Returns IssueState on success, FetchError on failure.
+ """
+ url = ISSUE_ENDPOINT.format(user=user, repository=repository, number=number)
+ pulls_url = PR_ENDPOINT.format(user=user, repository=repository, number=number)
+
+ json_data, r = await self.fetch_data(url)
+
+ if r.status == 403:
+ if r.headers.get("X-RateLimit-Remaining") == "0":
+ log.info(f"Ratelimit reached while fetching {url}")
+ return FetchError(403, "Ratelimit reached, please retry in a few minutes.")
+ return FetchError(403, "Cannot access issue.")
+ elif r.status in (404, 410):
+ return FetchError(r.status, "Issue not found.")
+ elif r.status != 200:
+ return FetchError(r.status, "Error while fetching issue.")
+
+ # The initial API request is made to the issues API endpoint, which will return information
+ # if the issue or PR is present. However, the scope of information returned for PRs differs
+ # from issues: if the 'issues' key is present in the response then we can pull the data we
+ # need from the initial API call.
+ if "issues" in json_data["html_url"]:
+ if json_data.get("state") == "open":
+ emoji = Emojis.issue_open
+ else:
+ emoji = Emojis.issue_closed
+
+ # If the 'issues' key is not contained in the API response and there is no error code, then
+ # we know that a PR has been requested and a call to the pulls API endpoint is necessary
+ # to get the desired information for the PR.
+ else:
+ pull_data, _ = await self.fetch_data(pulls_url)
+ if pull_data["draft"]:
+ emoji = Emojis.pull_request_draft
+ elif pull_data["state"] == "open":
+ emoji = Emojis.pull_request_open
+ # When 'merged_at' is not None, this means that the state of the PR is merged
+ elif pull_data["merged_at"] is not None:
+ emoji = Emojis.pull_request_merged
+ else:
+ emoji = Emojis.pull_request_closed
+
+ issue_url = json_data.get("html_url")
- async def fetch_data(self, url: str) -> dict:
- """Retrieve data as a dictionary."""
- async with self.bot.http_session.get(url) as r:
- return await r.json()
+ return IssueState(repository, number, issue_url, json_data.get("title", ""), emoji)
+
+ @staticmethod
+ def format_embed(
+ results: t.List[t.Union[IssueState, FetchError]]
+ ) -> discord.Embed:
+ """Take a list of IssueState or FetchError and format a Discord embed for them."""
+ description_list = []
+
+ for result in results:
+ if isinstance(result, IssueState):
+ description_list.append(f"{result.emoji} [{result.title}]({result.url})")
+ elif isinstance(result, FetchError):
+ description_list.append(f":x: [{result.return_code}] {result.message}")
+
+ resp = discord.Embed(
+ colour=Colours.bright_green,
+ description="\n".join(description_list)
+ )
+
+ resp.set_author(name="GitHub")
+ return resp
@commands.group(name="github", aliases=("gh", "git"))
@commands.cooldown(1, 10, commands.BucketType.user)
@@ -33,11 +168,67 @@ class GithubInfo(commands.Cog):
if ctx.invoked_subcommand is None:
await invoke_help_command(ctx)
+ @commands.Cog.listener()
+ async def on_message(self, message: discord.Message) -> None:
+ """
+ Automatic issue linking.
+
+ Listener to retrieve issue(s) from a GitHub repository using automatic linking if matching <org>/<repo>#<issue>.
+ """
+ # Ignore bots
+ if message.author.bot:
+ return
+
+ issues = [
+ FoundIssue(*match.group("org", "repo", "number"))
+ for match in AUTOMATIC_REGEX.finditer(self.remove_codeblocks(message.content))
+ ]
+ links = []
+
+ if issues:
+ # Block this from working in DMs
+ if not message.guild:
+ return
+
+ log.trace(f"Found {issues = }")
+ # Remove duplicates
+ issues = set(issues)
+
+ if len(issues) > MAXIMUM_ISSUES:
+ embed = discord.Embed(
+ title=random.choice(ERROR_REPLIES),
+ color=Colours.soft_red,
+ description=f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})"
+ )
+ await message.channel.send(embed=embed, delete_after=5)
+ return
+
+ for repo_issue in issues:
+ result = await self.fetch_issue(
+ int(repo_issue.number),
+ repo_issue.repository,
+ repo_issue.organisation or "python-discord"
+ )
+ if isinstance(result, IssueState):
+ links.append(result)
+
+ if not links:
+ return
+
+ resp = self.format_embed(links)
+ await message.channel.send(embed=resp)
+
+ async def fetch_data(self, url: str) -> tuple[dict[str], ClientResponse]:
+ """Retrieve data as a dictionary and the response in a tuple."""
+ log.trace(f"Querying GH issues API: {url}")
+ async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as r:
+ return await r.json(), r
+
@github_group.command(name="user", aliases=("userinfo",))
async def github_user_info(self, ctx: commands.Context, username: str) -> None:
"""Fetches a user's GitHub information."""
async with ctx.typing():
- user_data = await self.fetch_data(f"{GITHUB_API_URL}/users/{quote_plus(username)}")
+ user_data, _ = await self.fetch_data(f"{GITHUB_API_URL}/users/{username}")
# User_data will not have a message key if the user exists
if "message" in user_data:
@@ -50,7 +241,7 @@ class GithubInfo(commands.Cog):
await ctx.send(embed=embed)
return
- org_data = await self.fetch_data(user_data["organizations_url"])
+ org_data, _ = await self.fetch_data(user_data["organizations_url"])
orgs = [f"[{org['login']}](https://github.com/{org['login']})" for org in org_data]
orgs_to_add = " | ".join(orgs)
@@ -67,7 +258,7 @@ class GithubInfo(commands.Cog):
embed = discord.Embed(
title=f"`{user_data['login']}`'s GitHub profile info",
description=f"```\n{user_data['bio']}\n```\n" if user_data["bio"] else "",
- colour=discord.Colour.blurple(),
+ colour=discord.Colour.og_blurple(),
url=user_data["html_url"],
timestamp=datetime.strptime(user_data["created_at"], "%Y-%m-%dT%H:%M:%SZ")
)
@@ -91,10 +282,7 @@ class GithubInfo(commands.Cog):
)
if user_data["type"] == "User":
- embed.add_field(
- name="Gists",
- value=f"[{gists}](https://gist.github.com/{quote_plus(username, safe='')})"
- )
+ embed.add_field(name="Gists", value=f"[{gists}](https://gist.github.com/{quote(username, safe='')})")
embed.add_field(
name=f"Organization{'s' if len(orgs)!=1 else ''}",
@@ -123,7 +311,7 @@ class GithubInfo(commands.Cog):
return
async with ctx.typing():
- repo_data = await self.fetch_data(f"{GITHUB_API_URL}/repos/{quote(repo)}")
+ repo_data, _ = await self.fetch_data(f"{GITHUB_API_URL}/repos/{quote(repo)}")
# There won't be a message key if this repo exists
if "message" in repo_data:
@@ -139,7 +327,7 @@ class GithubInfo(commands.Cog):
embed = discord.Embed(
title=repo_data["name"],
description=repo_data["description"],
- colour=discord.Colour.blurple(),
+ colour=discord.Colour.og_blurple(),
url=repo_data["html_url"]
)
diff --git a/bot/exts/utilities/issues.py b/bot/exts/utilities/issues.py
deleted file mode 100644
index 8a7ebed0..00000000
--- a/bot/exts/utilities/issues.py
+++ /dev/null
@@ -1,275 +0,0 @@
-import logging
-import random
-import re
-from dataclasses import dataclass
-from typing import Optional, Union
-
-import discord
-from discord.ext import commands
-
-from bot.bot import Bot
-from bot.constants import (
- Categories,
- Channels,
- Colours,
- ERROR_REPLIES,
- Emojis,
- NEGATIVE_REPLIES,
- Tokens,
- WHITELISTED_CHANNELS
-)
-from bot.utils.decorators import whitelist_override
-from bot.utils.extensions import invoke_help_command
-
-log = logging.getLogger(__name__)
-
-BAD_RESPONSE = {
- 404: "Issue/pull request not located! Please enter a valid number!",
- 403: "Rate limit has been hit! Please try again later!"
-}
-REQUEST_HEADERS = {
- "Accept": "application/vnd.github.v3+json"
-}
-
-REPOSITORY_ENDPOINT = "https://api.github.com/orgs/{org}/repos?per_page=100&type=public"
-ISSUE_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/issues/{number}"
-PR_ENDPOINT = "https://api.github.com/repos/{user}/{repository}/pulls/{number}"
-
-if GITHUB_TOKEN := Tokens.github:
- REQUEST_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}"
-
-WHITELISTED_CATEGORIES = (
- Categories.development, Categories.devprojects, Categories.media, Categories.staff
-)
-
-CODE_BLOCK_RE = re.compile(
- r"^`([^`\n]+)`" # Inline codeblock
- r"|```(.+?)```", # Multiline codeblock
- re.DOTALL | re.MULTILINE
-)
-
-# Maximum number of issues in one message
-MAXIMUM_ISSUES = 5
-
-# Regex used when looking for automatic linking in messages
-# regex101 of current regex https://regex101.com/r/V2ji8M/6
-AUTOMATIC_REGEX = re.compile(
- r"((?P<org>[a-zA-Z0-9][a-zA-Z0-9\-]{1,39})\/)?(?P<repo>[\w\-\.]{1,100})#(?P<number>[0-9]+)"
-)
-
-
-@dataclass
-class FoundIssue:
- """Dataclass representing an issue found by the regex."""
-
- organisation: Optional[str]
- repository: str
- number: str
-
- def __hash__(self) -> int:
- return hash((self.organisation, self.repository, self.number))
-
-
-@dataclass
-class FetchError:
- """Dataclass representing an error while fetching an issue."""
-
- return_code: int
- message: str
-
-
-@dataclass
-class IssueState:
- """Dataclass representing the state of an issue."""
-
- repository: str
- number: int
- url: str
- title: str
- emoji: str
-
-
-class Issues(commands.Cog):
- """Cog that allows users to retrieve issues from GitHub."""
-
- def __init__(self, bot: Bot):
- self.bot = bot
- self.repos = []
-
- @staticmethod
- def remove_codeblocks(message: str) -> str:
- """Remove any codeblock in a message."""
- return re.sub(CODE_BLOCK_RE, "", message)
-
- async def fetch_issues(
- self,
- number: int,
- repository: str,
- user: str
- ) -> Union[IssueState, FetchError]:
- """
- Retrieve an issue from a GitHub repository.
-
- Returns IssueState on success, FetchError on failure.
- """
- url = ISSUE_ENDPOINT.format(user=user, repository=repository, number=number)
- pulls_url = PR_ENDPOINT.format(user=user, repository=repository, number=number)
- log.trace(f"Querying GH issues API: {url}")
-
- async with self.bot.http_session.get(url, headers=REQUEST_HEADERS) as r:
- json_data = await r.json()
-
- if r.status == 403:
- if r.headers.get("X-RateLimit-Remaining") == "0":
- log.info(f"Ratelimit reached while fetching {url}")
- return FetchError(403, "Ratelimit reached, please retry in a few minutes.")
- return FetchError(403, "Cannot access issue.")
- elif r.status in (404, 410):
- return FetchError(r.status, "Issue not found.")
- elif r.status != 200:
- return FetchError(r.status, "Error while fetching issue.")
-
- # The initial API request is made to the issues API endpoint, which will return information
- # if the issue or PR is present. However, the scope of information returned for PRs differs
- # from issues: if the 'issues' key is present in the response then we can pull the data we
- # need from the initial API call.
- if "issues" in json_data["html_url"]:
- if json_data.get("state") == "open":
- emoji = Emojis.issue_open
- else:
- emoji = Emojis.issue_closed
-
- # If the 'issues' key is not contained in the API response and there is no error code, then
- # we know that a PR has been requested and a call to the pulls API endpoint is necessary
- # to get the desired information for the PR.
- else:
- log.trace(f"PR provided, querying GH pulls API for additional information: {pulls_url}")
- async with self.bot.http_session.get(pulls_url) as p:
- pull_data = await p.json()
- if pull_data["draft"]:
- emoji = Emojis.pull_request_draft
- elif pull_data["state"] == "open":
- emoji = Emojis.pull_request_open
- # When 'merged_at' is not None, this means that the state of the PR is merged
- elif pull_data["merged_at"] is not None:
- emoji = Emojis.pull_request_merged
- else:
- emoji = Emojis.pull_request_closed
-
- issue_url = json_data.get("html_url")
-
- return IssueState(repository, number, issue_url, json_data.get("title", ""), emoji)
-
- @staticmethod
- def format_embed(
- results: list[Union[IssueState, FetchError]],
- user: str,
- repository: Optional[str] = None
- ) -> discord.Embed:
- """Take a list of IssueState or FetchError and format a Discord embed for them."""
- description_list = []
-
- for result in results:
- if isinstance(result, IssueState):
- description_list.append(f"{result.emoji} [{result.title}]({result.url})")
- elif isinstance(result, FetchError):
- description_list.append(f":x: [{result.return_code}] {result.message}")
-
- resp = discord.Embed(
- colour=Colours.bright_green,
- description="\n".join(description_list)
- )
-
- embed_url = f"https://github.com/{user}/{repository}" if repository else f"https://github.com/{user}"
- resp.set_author(name="GitHub", url=embed_url)
- return resp
-
- @whitelist_override(channels=WHITELISTED_CHANNELS, categories=WHITELISTED_CATEGORIES)
- @commands.command(aliases=("pr",))
- async def issue(
- self,
- ctx: commands.Context,
- numbers: commands.Greedy[int],
- repository: str = "sir-lancebot",
- user: str = "python-discord"
- ) -> None:
- """Command to retrieve issue(s) from a GitHub repository."""
- # Remove duplicates
- numbers = set(numbers)
-
- if len(numbers) > MAXIMUM_ISSUES:
- embed = discord.Embed(
- title=random.choice(ERROR_REPLIES),
- color=Colours.soft_red,
- description=f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})"
- )
- await ctx.send(embed=embed)
- await invoke_help_command(ctx)
-
- results = [await self.fetch_issues(number, repository, user) for number in numbers]
- await ctx.send(embed=self.format_embed(results, user, repository))
-
- @commands.Cog.listener()
- async def on_message(self, message: discord.Message) -> None:
- """
- Automatic issue linking.
-
- Listener to retrieve issue(s) from a GitHub repository using automatic linking if matching <org>/<repo>#<issue>.
- """
- # Ignore bots
- if message.author.bot:
- return
-
- issues = [
- FoundIssue(*match.group("org", "repo", "number"))
- for match in AUTOMATIC_REGEX.finditer(self.remove_codeblocks(message.content))
- ]
- links = []
-
- if issues:
- # Block this from working in DMs
- if not message.guild:
- await message.channel.send(
- embed=discord.Embed(
- title=random.choice(NEGATIVE_REPLIES),
- description=(
- "You can't retrieve issues from DMs. "
- f"Try again in <#{Channels.community_bot_commands}>"
- ),
- colour=Colours.soft_red
- )
- )
- return
-
- log.trace(f"Found {issues = }")
- # Remove duplicates
- issues = set(issues)
-
- if len(issues) > MAXIMUM_ISSUES:
- embed = discord.Embed(
- title=random.choice(ERROR_REPLIES),
- color=Colours.soft_red,
- description=f"Too many issues/PRs! (maximum of {MAXIMUM_ISSUES})"
- )
- await message.channel.send(embed=embed, delete_after=5)
- return
-
- for repo_issue in issues:
- result = await self.fetch_issues(
- int(repo_issue.number),
- repo_issue.repository,
- repo_issue.organisation or "python-discord"
- )
- if isinstance(result, IssueState):
- links.append(result)
-
- if not links:
- return
-
- resp = self.format_embed(links, "python-discord")
- await message.channel.send(embed=resp)
-
-
-def setup(bot: Bot) -> None:
- """Load the Issues cog."""
- bot.add_cog(Issues(bot))
diff --git a/bot/exts/utilities/latex.py b/bot/exts/utilities/latex.py
deleted file mode 100644
index 36c7e0ab..00000000
--- a/bot/exts/utilities/latex.py
+++ /dev/null
@@ -1,101 +0,0 @@
-import asyncio
-import hashlib
-import pathlib
-import re
-from concurrent.futures import ThreadPoolExecutor
-from io import BytesIO
-
-import discord
-import matplotlib.pyplot as plt
-from discord.ext import commands
-
-from bot.bot import Bot
-
-# configure fonts and colors for matplotlib
-plt.rcParams.update(
- {
- "font.size": 16,
- "mathtext.fontset": "cm", # Computer Modern font set
- "mathtext.rm": "serif",
- "figure.facecolor": "36393F", # matches Discord's dark mode background color
- "text.color": "white",
- }
-)
-
-FORMATTED_CODE_REGEX = re.compile(
- r"(?P<delim>(?P<block>```)|``?)" # code delimiter: 1-3 backticks; (?P=block) only matches if it's a block
- r"(?(block)(?:(?P<lang>[a-z]+)\n)?)" # if we're in a block, match optional language (only letters plus newline)
- r"(?:[ \t]*\n)*" # any blank (empty or tabs/spaces only) lines before the code
- r"(?P<code>.*?)" # extract all code inside the markup
- r"\s*" # any more whitespace before the end of the code markup
- r"(?P=delim)", # match the exact same delimiter from the start again
- re.DOTALL | re.IGNORECASE, # "." also matches newlines, case insensitive
-)
-
-CACHE_DIRECTORY = pathlib.Path("_latex_cache")
-CACHE_DIRECTORY.mkdir(exist_ok=True)
-
-
-class Latex(commands.Cog):
- """Renders latex."""
-
- @staticmethod
- def _render(text: str, filepath: pathlib.Path) -> BytesIO:
- """
- Return the rendered image if latex compiles without errors, otherwise raise a BadArgument Exception.
-
- Saves rendered image to cache.
- """
- fig = plt.figure()
- rendered_image = BytesIO()
- fig.text(0, 1, text, horizontalalignment="left", verticalalignment="top")
-
- try:
- plt.savefig(rendered_image, bbox_inches="tight", dpi=600)
- except ValueError as e:
- raise commands.BadArgument(str(e))
-
- rendered_image.seek(0)
-
- with open(filepath, "wb") as f:
- f.write(rendered_image.getbuffer())
-
- return rendered_image
-
- @staticmethod
- def _prepare_input(text: str) -> str:
- text = text.replace(r"\\", "$\n$") # matplotlib uses \n for newlines, not \\
-
- if match := FORMATTED_CODE_REGEX.match(text):
- return match.group("code")
- else:
- return text
-
- @commands.command()
- @commands.max_concurrency(1, commands.BucketType.guild, wait=True)
- async def latex(self, ctx: commands.Context, *, text: str) -> None:
- """Renders the text in latex and sends the image."""
- text = self._prepare_input(text)
- query_hash = hashlib.md5(text.encode()).hexdigest()
- image_path = CACHE_DIRECTORY.joinpath(f"{query_hash}.png")
- async with ctx.typing():
- if image_path.exists():
- await ctx.send(file=discord.File(image_path))
- return
-
- with ThreadPoolExecutor() as pool:
- image = await asyncio.get_running_loop().run_in_executor(
- pool, self._render, text, image_path
- )
-
- await ctx.send(file=discord.File(image, "latex.png"))
-
-
-def setup(bot: Bot) -> None:
- """Load the Latex Cog."""
- # As we have resource issues on this cog,
- # we have it currently disabled while we fix it.
- import logging
- logging.info("Latex cog is currently disabled. It won't be loaded.")
- return
- bot.add_cog(Latex())
diff --git a/bot/exts/utilities/realpython.py b/bot/exts/utilities/realpython.py
index ef8b2638..bf8f1341 100644
--- a/bot/exts/utilities/realpython.py
+++ b/bot/exts/utilities/realpython.py
@@ -1,5 +1,6 @@
import logging
from html import unescape
+from typing import Optional
from urllib.parse import quote_plus
from discord import Embed
@@ -31,9 +32,18 @@ class RealPython(commands.Cog):
@commands.command(aliases=["rp"])
@commands.cooldown(1, 10, commands.cooldowns.BucketType.user)
- async def realpython(self, ctx: commands.Context, *, user_search: str) -> None:
- """Send 5 articles that match the user's search terms."""
- params = {"q": user_search, "limit": 5, "kind": "article"}
+ async def realpython(self, ctx: commands.Context, amount: Optional[int] = 5, *, user_search: str) -> None:
+ """
+ Send some articles from RealPython that match the search terms.
+
+ By default the top 5 matches are sent, this can be overwritten to
+ a number between 1 and 5 by specifying an amount before the search query.
+ """
+ if not 1 <= amount <= 5:
+ await ctx.send("`amount` must be between 1 and 5 (inclusive).")
+ return
+
+ params = {"q": user_search, "limit": amount, "kind": "article"}
async with self.bot.http_session.get(url=API_ROOT, params=params) as response:
if response.status != 200:
logger.error(
diff --git a/bot/exts/utilities/reddit.py b/bot/exts/utilities/reddit.py
index e6cb5337..782583d2 100644
--- a/bot/exts/utilities/reddit.py
+++ b/bot/exts/utilities/reddit.py
@@ -244,7 +244,7 @@ class Reddit(Cog):
# Use only starting summary page for #reddit channel posts.
embed.description = self.build_pagination_pages(posts, paginate=False)
- embed.colour = Colour.blurple()
+ embed.colour = Colour.og_blurple()
return embed
@loop()
@@ -312,7 +312,7 @@ class Reddit(Cog):
await ctx.send(f"Here are the top {subreddit} posts of all time!")
embed = Embed(
- color=Colour.blurple()
+ color=Colour.og_blurple()
)
await ImagePaginator.paginate(pages, ctx, embed)
@@ -325,7 +325,7 @@ class Reddit(Cog):
await ctx.send(f"Here are today's top {subreddit} posts!")
embed = Embed(
- color=Colour.blurple()
+ color=Colour.og_blurple()
)
await ImagePaginator.paginate(pages, ctx, embed)
@@ -338,7 +338,7 @@ class Reddit(Cog):
await ctx.send(f"Here are this week's top {subreddit} posts!")
embed = Embed(
- color=Colour.blurple()
+ color=Colour.og_blurple()
)
await ImagePaginator.paginate(pages, ctx, embed)
@@ -349,7 +349,7 @@ class Reddit(Cog):
"""Send a paginated embed of all the subreddits we're relaying."""
embed = Embed()
embed.title = "Relayed subreddits."
- embed.colour = Colour.blurple()
+ embed.colour = Colour.og_blurple()
await LinePaginator.paginate(
RedditConfig.subreddits,
diff --git a/bot/exts/utilities/twemoji.py b/bot/exts/utilities/twemoji.py
new file mode 100644
index 00000000..c915f05b
--- /dev/null
+++ b/bot/exts/utilities/twemoji.py
@@ -0,0 +1,150 @@
+import logging
+import re
+from typing import Literal, Optional
+
+import discord
+from discord.ext import commands
+from emoji import UNICODE_EMOJI_ENGLISH, is_emoji
+
+from bot.bot import Bot
+from bot.constants import Colours, Roles
+from bot.utils.decorators import whitelist_override
+from bot.utils.extensions import invoke_help_command
+
+log = logging.getLogger(__name__)
+BASE_URLS = {
+ "png": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/",
+ "svg": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/",
+}
+CODEPOINT_REGEX = re.compile(r"[a-f1-9][a-f0-9]{3,5}$")
+
+
+class Twemoji(commands.Cog):
+ """Utilities for working with Twemojis."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ @staticmethod
+ def get_url(codepoint: str, format: Literal["png", "svg"]) -> str:
+ """Returns a source file URL for the specified Twemoji, in the corresponding format."""
+ return f"{BASE_URLS[format]}{codepoint}.{format}"
+
+ @staticmethod
+ def alias_to_name(alias: str) -> str:
+ """
+ Transform a unicode alias to an emoji name.
+
+ Example usages:
+ >>> alias_to_name(":falling_leaf:")
+ "Falling leaf"
+ >>> alias_to_name(":family_man_girl_boy:")
+ "Family man girl boy"
+ """
+ name = alias.strip(":").replace("_", " ")
+ return name.capitalize()
+
+ @staticmethod
+ def build_embed(codepoint: str) -> discord.Embed:
+ """Returns the main embed for the `twemoji` commmand."""
+ emoji = "".join(Twemoji.emoji(e) or "" for e in codepoint.split("-"))
+
+ embed = discord.Embed(
+ title=Twemoji.alias_to_name(UNICODE_EMOJI_ENGLISH[emoji]),
+ description=f"{codepoint.replace('-', ' ')}\n[Download svg]({Twemoji.get_url(codepoint, 'svg')})",
+ colour=Colours.twitter_blue,
+ )
+ embed.set_thumbnail(url=Twemoji.get_url(codepoint, "png"))
+ return embed
+
+ @staticmethod
+ def emoji(codepoint: Optional[str]) -> Optional[str]:
+ """
+ Returns the emoji corresponding to a given `codepoint`, or `None` if no emoji was found.
+
+ The return value is an emoji character, such as "🍂". The `codepoint`
+ argument can be of any format, since it will be trimmed automatically.
+ """
+ if code := Twemoji.trim_code(codepoint):
+ return chr(int(code, 16))
+
+ @staticmethod
+ def codepoint(emoji: Optional[str]) -> Optional[str]:
+ """
+ Returns the codepoint, in a trimmed format, of a single emoji.
+
+ `emoji` should be an emoji character, such as "🐍" and "🥰", and
+ not a codepoint like "1f1f8". When working with combined emojis,
+ such as "🇸🇪" and "👨‍👩‍👦", send the component emojis through the method
+ one at a time.
+ """
+ if emoji is None:
+ return None
+ return hex(ord(emoji)).removeprefix("0x")
+
+ @staticmethod
+ def trim_code(codepoint: Optional[str]) -> Optional[str]:
+ """
+ Returns the meaningful information from the given `codepoint`.
+
+ If no codepoint is found, `None` is returned.
+
+ Example usages:
+ >>> trim_code("U+1f1f8")
+ "1f1f8"
+ >>> trim_code("\u0001f1f8")
+ "1f1f8"
+ >>> trim_code("1f466")
+ "1f466"
+ """
+ if code := CODEPOINT_REGEX.search(codepoint or ""):
+ return code.group()
+
+ @staticmethod
+ def codepoint_from_input(raw_emoji: tuple[str, ...]) -> str:
+ """
+ Returns the codepoint corresponding to the passed tuple, separated by "-".
+
+ The return format matches the format used in URLs for Twemoji source files.
+
+ Example usages:
+ >>> codepoint_from_input(("🐍",))
+ "1f40d"
+ >>> codepoint_from_input(("1f1f8", "1f1ea"))
+ "1f1f8-1f1ea"
+ >>> codepoint_from_input(("👨‍👧‍👦",))
+ "1f468-200d-1f467-200d-1f466"
+ """
+ raw_emoji = [emoji.lower() for emoji in raw_emoji]
+ if is_emoji(raw_emoji[0]):
+ emojis = (Twemoji.codepoint(emoji) or "" for emoji in raw_emoji[0])
+ return "-".join(emojis)
+
+ emoji = "".join(
+ Twemoji.emoji(Twemoji.trim_code(code)) or "" for code in raw_emoji
+ )
+ if is_emoji(emoji):
+ return "-".join(Twemoji.codepoint(e) or "" for e in emoji)
+
+ raise ValueError("No codepoint could be obtained from the given input")
+
+ @commands.command(aliases=("tw",))
+ @whitelist_override(roles=(Roles.everyone,))
+ async def twemoji(self, ctx: commands.Context, *raw_emoji: str) -> None:
+ """Sends a preview of a given Twemoji, specified by codepoint or emoji."""
+ if len(raw_emoji) == 0:
+ await invoke_help_command(ctx)
+ return
+ try:
+ codepoint = self.codepoint_from_input(raw_emoji)
+ except ValueError:
+ raise commands.BadArgument(
+ "please include a valid emoji or emoji codepoint."
+ )
+
+ await ctx.send(embed=self.build_embed(codepoint))
+
+
+def setup(bot: Bot) -> None:
+ """Load the Twemoji cog."""
+ bot.add_cog(Twemoji(bot))
diff --git a/bot/exts/utilities/wikipedia.py b/bot/exts/utilities/wikipedia.py
index eccc1f8c..e5e8e289 100644
--- a/bot/exts/utilities/wikipedia.py
+++ b/bot/exts/utilities/wikipedia.py
@@ -82,13 +82,11 @@ class WikipediaSearch(commands.Cog):
if contents:
embed = Embed(
title="Wikipedia Search Results",
- colour=Color.blurple()
+ colour=Color.og_blurple()
)
embed.set_thumbnail(url=WIKI_THUMBNAIL)
embed.timestamp = datetime.utcnow()
- await LinePaginator.paginate(
- contents, ctx, embed
- )
+ await LinePaginator.paginate(contents, ctx, embed, restrict_to_user=ctx.author)
else:
await ctx.send(
"Sorry, we could not find a wikipedia article using that search term."
diff --git a/bot/exts/utilities/wtf_python.py b/bot/exts/utilities/wtf_python.py
new file mode 100644
index 00000000..980b3dba
--- /dev/null
+++ b/bot/exts/utilities/wtf_python.py
@@ -0,0 +1,138 @@
+import logging
+import random
+import re
+from typing import Optional
+
+import rapidfuzz
+from discord import Embed, File
+from discord.ext import commands, tasks
+
+from bot import constants
+from bot.bot import Bot
+
+log = logging.getLogger(__name__)
+
+WTF_PYTHON_RAW_URL = "http://raw.githubusercontent.com/satwikkansal/wtfpython/master/"
+BASE_URL = "https://github.com/satwikkansal/wtfpython"
+LOGO_PATH = "./bot/resources/utilities/wtf_python_logo.jpg"
+
+ERROR_MESSAGE = f"""
+Unknown WTF Python Query. Please try to reformulate your query.
+
+**Examples**:
+```md
+{constants.Client.prefix}wtf wild imports
+{constants.Client.prefix}wtf subclass
+{constants.Client.prefix}wtf del
+```
+If the problem persists send a message in <#{constants.Channels.dev_contrib}>
+"""
+
+MINIMUM_CERTAINTY = 55
+
+
+class WTFPython(commands.Cog):
+ """Cog that allows getting WTF Python entries from the WTF Python repository."""
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+ self.headers: dict[str, str] = {}
+ self.fetch_readme.start()
+
+ @tasks.loop(minutes=60)
+ async def fetch_readme(self) -> None:
+ """Gets the content of README.md from the WTF Python Repository."""
+ async with self.bot.http_session.get(f"{WTF_PYTHON_RAW_URL}README.md") as resp:
+ log.trace("Fetching the latest WTF Python README.md")
+ if resp.status == 200:
+ raw = await resp.text()
+ self.parse_readme(raw)
+
+ def parse_readme(self, data: str) -> None:
+ """
+ Parses the README.md into a dict.
+
+ It parses the readme into the `self.headers` dict,
+ where the key is the heading and the value is the
+ link to the heading.
+ """
+ # Match the start of examples, until the end of the table of contents (toc)
+ table_of_contents = re.search(
+ r"\[👀 Examples\]\(#-examples\)\n([\w\W]*)<!-- tocstop -->", data
+ )[0].split("\n")
+
+ for header in list(map(str.strip, table_of_contents)):
+ match = re.search(r"\[▶ (.*)\]\((.*)\)", header)
+ if match:
+ hyper_link = match[0].split("(")[1].replace(")", "")
+ self.headers[match[0]] = f"{BASE_URL}/{hyper_link}"
+
+ def fuzzy_match_header(self, query: str) -> Optional[str]:
+ """
+ Returns the fuzzy match of a query if its ratio is above "MINIMUM_CERTAINTY" else returns None.
+
+ "MINIMUM_CERTAINTY" is the lowest score at which the fuzzy match will return a result.
+ The certainty returned by rapidfuzz.process.extractOne is a score between 0 and 100,
+ with 100 being a perfect match.
+ """
+ match, certainty, _ = rapidfuzz.process.extractOne(query, self.headers.keys())
+ return match if certainty > MINIMUM_CERTAINTY else None
+
+ @commands.command(aliases=("wtf", "WTF"))
+ async def wtf_python(self, ctx: commands.Context, *, query: Optional[str] = None) -> None:
+ """
+ Search WTF Python repository.
+
+ Gets the link of the fuzzy matched query from https://github.com/satwikkansal/wtfpython.
+ Usage:
+ --> .wtf wild imports
+ """
+ if query is None:
+ no_query_embed = Embed(
+ title="WTF Python?!",
+ colour=constants.Colours.dark_green,
+ description="A repository filled with suprising snippets that can make you say WTF?!\n\n"
+ f"[Go to the Repository]({BASE_URL})"
+ )
+ logo = File(LOGO_PATH, filename="wtf_logo.jpg")
+ no_query_embed.set_thumbnail(url="attachment://wtf_logo.jpg")
+ await ctx.send(embed=no_query_embed, file=logo)
+ return
+
+ if len(query) > 50:
+ embed = Embed(
+ title=random.choice(constants.ERROR_REPLIES),
+ description=ERROR_MESSAGE,
+ colour=constants.Colours.soft_red,
+ )
+ match = None
+ else:
+ match = self.fuzzy_match_header(query)
+
+ if not match:
+ embed = Embed(
+ title=random.choice(constants.ERROR_REPLIES),
+ description=ERROR_MESSAGE,
+ colour=constants.Colours.soft_red,
+ )
+ await ctx.send(embed=embed)
+ return
+
+ embed = Embed(
+ title="WTF Python?!",
+ colour=constants.Colours.dark_green,
+ description=f"""Search result for '{query}': {match.split("]")[0].replace("[", "")}
+ [Go to Repository Section]({self.headers[match]})""",
+ )
+ logo = File(LOGO_PATH, filename="wtf_logo.jpg")
+ embed.set_thumbnail(url="attachment://wtf_logo.jpg")
+ await ctx.send(embed=embed, file=logo)
+
+ def cog_unload(self) -> None:
+ """Unload the cog and cancel the task."""
+ self.fetch_readme.cancel()
+
+
+def setup(bot: Bot) -> None:
+ """Load the WTFPython Cog."""
+ bot.add_cog(WTFPython(bot))
diff --git a/bot/group.py b/bot/group.py
deleted file mode 100644
index a7bc59b7..00000000
--- a/bot/group.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from discord.ext import commands
-
-
-class Group(commands.Group):
- """
- A `discord.ext.commands.Group` subclass which supports root aliases.
-
- A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as
- top-level groups rather than being aliases of the command's group. It's stored as an attribute
- also named `root_aliases`.
- """
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.root_aliases = kwargs.get("root_aliases", [])
-
- if not isinstance(self.root_aliases, (list, tuple)):
- raise TypeError("Root aliases of a group must be a list or a tuple of strings.")
diff --git a/bot/log.py b/bot/log.py
new file mode 100644
index 00000000..a87a836a
--- /dev/null
+++ b/bot/log.py
@@ -0,0 +1,96 @@
+import logging
+import logging.handlers
+import os
+import sys
+from pathlib import Path
+
+import coloredlogs
+
+from bot.constants import Logging
+
+
+def setup() -> None:
+ """Set up loggers."""
+ # Configure the "TRACE" logging level (e.g. "log.trace(message)")
+ logging.TRACE = 5
+ logging.addLevelName(logging.TRACE, "TRACE")
+ logging.Logger.trace = _monkeypatch_trace
+
+ format_string = "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
+ log_format = logging.Formatter(format_string)
+ root_logger = logging.getLogger()
+
+ if Logging.file_logs:
+ # Set up file logging
+ log_file = Path("logs/sir-lancebot.log")
+ log_file.parent.mkdir(exist_ok=True)
+
+ # File handler rotates logs every 5 MB
+ file_handler = logging.handlers.RotatingFileHandler(
+ log_file, maxBytes=5 * (2 ** 20), backupCount=10, encoding="utf-8",
+ )
+ file_handler.setFormatter(log_format)
+ root_logger.addHandler(file_handler)
+
+ if "COLOREDLOGS_LEVEL_STYLES" not in os.environ:
+ coloredlogs.DEFAULT_LEVEL_STYLES = {
+ **coloredlogs.DEFAULT_LEVEL_STYLES,
+ "trace": {"color": 246},
+ "critical": {"background": "red"},
+ "debug": coloredlogs.DEFAULT_LEVEL_STYLES["info"],
+ }
+
+ if "COLOREDLOGS_LOG_FORMAT" not in os.environ:
+ coloredlogs.DEFAULT_LOG_FORMAT = format_string
+
+ coloredlogs.install(level=logging.TRACE, stream=sys.stdout)
+
+ root_logger.setLevel(logging.DEBUG if Logging.debug else logging.INFO)
+ # Silence irrelevant loggers
+ logging.getLogger("discord").setLevel(logging.ERROR)
+ logging.getLogger("websockets").setLevel(logging.ERROR)
+ logging.getLogger("PIL").setLevel(logging.ERROR)
+ logging.getLogger("matplotlib").setLevel(logging.ERROR)
+ logging.getLogger("async_rediscache").setLevel(logging.WARNING)
+
+ _set_trace_loggers()
+
+ root_logger.info("Logging initialization complete")
+
+
+def _monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None:
+ """
+ Log 'msg % args' with severity 'TRACE'.
+
+ To pass exception information, use the keyword argument exc_info with a true value, e.g.
+ logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
+ """
+ if self.isEnabledFor(logging.TRACE):
+ self._log(logging.TRACE, msg, args, **kwargs)
+
+
+def _set_trace_loggers() -> None:
+ """
+ Set loggers to the trace level according to the value from the BOT_TRACE_LOGGERS env var.
+
+ When the env var is a list of logger names delimited by a comma,
+ each of the listed loggers will be set to the trace level.
+
+ If this list is prefixed with a "!", all of the loggers except the listed ones will be set to the trace level.
+
+ Otherwise if the env var begins with a "*",
+ the root logger is set to the trace level and other contents are ignored.
+ """
+ level_filter = Logging.trace_loggers
+ if level_filter:
+ if level_filter.startswith("*"):
+ logging.getLogger().setLevel(logging.TRACE)
+
+ elif level_filter.startswith("!"):
+ logging.getLogger().setLevel(logging.TRACE)
+ for logger_name in level_filter.strip("!,").split(","):
+ logging.getLogger(logger_name).setLevel(logging.DEBUG)
+
+ else:
+ for logger_name in level_filter.strip(",").split(","):
+ logging.getLogger(logger_name).setLevel(logging.TRACE)
diff --git a/bot/monkey_patches.py b/bot/monkey_patches.py
new file mode 100644
index 00000000..925d3206
--- /dev/null
+++ b/bot/monkey_patches.py
@@ -0,0 +1,91 @@
+import logging
+import re
+from datetime import datetime, timedelta
+
+from discord import Forbidden, http
+from discord.ext import commands
+
+log = logging.getLogger(__name__)
+MESSAGE_ID_RE = re.compile(r"(?P<message_id>[0-9]{15,20})$")
+
+
+class Command(commands.Command):
+ """
+ A `discord.ext.commands.Command` subclass which supports root aliases.
+
+ A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as
+ top-level commands rather than being aliases of the command's group. It's stored as an attribute
+ also named `root_aliases`.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.root_aliases = kwargs.get("root_aliases", [])
+
+ if not isinstance(self.root_aliases, (list, tuple)):
+ raise TypeError("Root aliases of a command must be a list or a tuple of strings.")
+
+
+class Group(commands.Group):
+ """
+ A `discord.ext.commands.Group` subclass which supports root aliases.
+
+ A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as
+ top-level groups rather than being aliases of the command's group. It's stored as an attribute
+ also named `root_aliases`.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.root_aliases = kwargs.get("root_aliases", [])
+
+ if not isinstance(self.root_aliases, (list, tuple)):
+ raise TypeError("Root aliases of a group must be a list or a tuple of strings.")
+
+
+def patch_typing() -> None:
+ """
+ Sometimes discord turns off typing events by throwing 403's.
+
+ Handle those issues by patching the trigger_typing method so it ignores 403's in general.
+ """
+ log.debug("Patching send_typing, which should fix things breaking when discord disables typing events. Stay safe!")
+
+ original = http.HTTPClient.send_typing
+ last_403 = None
+
+ async def honeybadger_type(self, channel_id: int) -> None: # noqa: ANN001
+ nonlocal last_403
+ if last_403 and (datetime.utcnow() - last_403) < timedelta(minutes=5):
+ log.warning("Not sending typing event, we got a 403 less than 5 minutes ago.")
+ return
+ try:
+ await original(self, channel_id)
+ except Forbidden:
+ last_403 = datetime.utcnow()
+ log.warning("Got a 403 from typing event!")
+ pass
+
+ http.HTTPClient.send_typing = honeybadger_type
+
+
+class FixedPartialMessageConverter(commands.PartialMessageConverter):
+ """
+ Make the Message converter infer channelID from the given context if only a messageID is given.
+
+ Discord.py's Message converter is supposed to infer channelID based
+ on ctx.channel if only a messageID is given. A refactor commit, linked below,
+ a few weeks before d.py's archival broke this defined behaviour of the converter.
+ Currently, if only a messageID is given to the converter, it will only find that message
+ if it's in the bot's cache.
+
+ https://github.com/Rapptz/discord.py/commit/1a4e73d59932cdbe7bf2c281f25e32529fc7ae1f
+ """
+
+ @staticmethod
+ def _get_id_matches(ctx: commands.Context, argument: str) -> tuple[int, int, int]:
+ """Inserts ctx.channel.id before calling super method if argument is just a messageID."""
+ match = MESSAGE_ID_RE.match(argument)
+ if match:
+ argument = f"{ctx.channel.id}-{match.group('message_id')}"
+ return commands.PartialMessageConverter._get_id_matches(ctx, argument)
diff --git a/bot/resources/fun/anagram.json b/bot/resources/fun/anagram.json
new file mode 100644
index 00000000..ea5a5794
--- /dev/null
+++ b/bot/resources/fun/anagram.json
@@ -0,0 +1,17668 @@
+{
+ "eht": [
+ "eth",
+ "het",
+ "the"
+ ],
+ "adn": [
+ "and",
+ "dan",
+ "nad"
+ ],
+ "for": [
+ "for",
+ "fro",
+ "orf"
+ ],
+ "ahtt": [
+ "hatt",
+ "tath",
+ "that"
+ ],
+ "hist": [
+ "hist",
+ "hits",
+ "isth",
+ "shit",
+ "sith",
+ "this",
+ "tshi"
+ ],
+ "hitw": [
+ "whit",
+ "with"
+ ],
+ "not": [
+ "not",
+ "ont",
+ "ton"
+ ],
+ "aer": [
+ "aer",
+ "are",
+ "ear",
+ "era",
+ "rea"
+ ],
+ "fmor": [
+ "form",
+ "from"
+ ],
+ "enw": [
+ "new",
+ "wen"
+ ],
+ "emor": [
+ "mero",
+ "more",
+ "omer",
+ "rome"
+ ],
+ "asw": [
+ "saw",
+ "swa",
+ "was"
+ ],
+ "acn": [
+ "anc",
+ "can"
+ ],
+ "aegp": [
+ "gape",
+ "page",
+ "peag",
+ "pega"
+ ],
+ "ahs": [
+ "ahs",
+ "ash",
+ "has",
+ "sah",
+ "sha"
+ ],
+ "acehrs": [
+ "arches",
+ "ascher",
+ "casher",
+ "chares",
+ "chaser",
+ "eschar",
+ "raches",
+ "recash",
+ "search"
+ ],
+ "eefr": [
+ "feer",
+ "fere",
+ "free",
+ "reef"
+ ],
+ "btu": [
+ "btu",
+ "but",
+ "tub"
+ ],
+ "oru": [
+ "our",
+ "uro"
+ ],
+ "eno": [
+ "eon",
+ "neo",
+ "one"
+ ],
+ "ehort": [
+ "other",
+ "theor",
+ "thore",
+ "throe",
+ "toher"
+ ],
+ "eimt": [
+ "emit",
+ "item",
+ "mite",
+ "time"
+ ],
+ "ehty": [
+ "hyte",
+ "yeth",
+ "they"
+ ],
+ "eist": [
+ "seit",
+ "site",
+ "ties"
+ ],
+ "amy": [
+ "amy",
+ "yam",
+ "may",
+ "mya"
+ ],
+ "ahtw": [
+ "thaw",
+ "wath",
+ "what"
+ ],
+ "ehirt": [
+ "ither",
+ "rithe",
+ "their"
+ ],
+ "ensw": [
+ "news",
+ "sewn",
+ "snew",
+ "wens"
+ ],
+ "otu": [
+ "out",
+ "tou"
+ ],
+ "esu": [
+ "esu",
+ "sue",
+ "use"
+ ],
+ "any": [
+ "any",
+ "yan",
+ "nay"
+ ],
+ "eehrt": [
+ "ether",
+ "rethe",
+ "theer",
+ "there",
+ "three"
+ ],
+ "ees": [
+ "ese",
+ "see"
+ ],
+ "lnoy": [
+ "lyon",
+ "loyn",
+ "only"
+ ],
+ "his": [
+ "his",
+ "hsi",
+ "ihs",
+ "ish",
+ "shi"
+ ],
+ "ehnw": [
+ "hewn",
+ "when"
+ ],
+ "eehr": [
+ "heer",
+ "here"
+ ],
+ "how": [
+ "how",
+ "who"
+ ],
+ "alos": [
+ "also",
+ "laos",
+ "sola"
+ ],
+ "now": [
+ "now",
+ "own",
+ "won"
+ ],
+ "egt": [
+ "get",
+ "gte",
+ "teg"
+ ],
+ "eivw": [
+ "view",
+ "wive"
+ ],
+ "eilnno": [
+ "lionne",
+ "online"
+ ],
+ "first": [
+ "first",
+ "frist",
+ "frits",
+ "rifts"
+ ],
+ "been": [
+ "been",
+ "bene",
+ "eben"
+ ],
+ "eerw": [
+ "ewer",
+ "weer",
+ "were"
+ ],
+ "ceeirssv": [
+ "scrieves",
+ "services"
+ ],
+ "emos": [
+ "meso",
+ "mose",
+ "some"
+ ],
+ "eehst": [
+ "sheet",
+ "these"
+ ],
+ "ist": [
+ "ist",
+ "its",
+ "sit",
+ "tis",
+ "tsi"
+ ],
+ "eikl": [
+ "kiel",
+ "like"
+ ],
+ "ceeirsv": [
+ "cerevis",
+ "cresive",
+ "scrieve",
+ "service"
+ ],
+ "ahnt": [
+ "hant",
+ "tanh",
+ "than"
+ ],
+ "ceipr": [
+ "price",
+ "recip",
+ "repic"
+ ],
+ "adet": [
+ "adet",
+ "ated",
+ "date",
+ "tade",
+ "tead",
+ "teda"
+ ],
+ "opt": [
+ "opt",
+ "pot",
+ "top"
+ ],
+ "adh": [
+ "dah",
+ "dha",
+ "had"
+ ],
+ "ilst": [
+ "list",
+ "lits",
+ "silt",
+ "slit",
+ "tils"
+ ],
+ "aemn": [
+ "amen",
+ "enam",
+ "mane",
+ "mean",
+ "name",
+ "nema"
+ ],
+ "jstu": [
+ "just",
+ "juts"
+ ],
+ "eorv": [
+ "over",
+ "rove"
+ ],
+ "aestt": [
+ "state",
+ "taste",
+ "tates",
+ "teats",
+ "testa"
+ ],
+ "aery": [
+ "aery",
+ "ayre",
+ "eyra",
+ "yare",
+ "year"
+ ],
+ "ady": [
+ "ady",
+ "day",
+ "yad"
+ ],
+ "inot": [
+ "into",
+ "nito",
+ "oint",
+ "tino"
+ ],
+ "aeilm": [
+ "email",
+ "maile",
+ "malie",
+ "melia"
+ ],
+ "otw": [
+ "owt",
+ "tow",
+ "two",
+ "wot"
+ ],
+ "desu": [
+ "deus",
+ "dues",
+ "sude",
+ "sued",
+ "used"
+ ],
+ "alst": [
+ "alts",
+ "last",
+ "lats",
+ "salt",
+ "slat"
+ ],
+ "most": [
+ "most",
+ "mots",
+ "toms"
+ ],
+ "cimsu": [
+ "musci",
+ "music"
+ ],
+ "aadt": [
+ "adat",
+ "data"
+ ],
+ "aekm": [
+ "kame",
+ "make",
+ "meak"
+ ],
+ "ehmt": [
+ "meth",
+ "them"
+ ],
+ "emssty": [
+ "mystes",
+ "system"
+ ],
+ "opst": [
+ "opts",
+ "post",
+ "pots",
+ "spot",
+ "stop",
+ "tops"
+ ],
+ "ehr": [
+ "her",
+ "reh",
+ "rhe"
+ ],
+ "add": [
+ "add",
+ "dad"
+ ],
+ "chsu": [
+ "cush",
+ "such"
+ ],
+ "aeelps": [
+ "asleep",
+ "elapse",
+ "please",
+ "sapele"
+ ],
+ "aeegmss": [
+ "megasse",
+ "message"
+ ],
+ "aefrt": [
+ "afret",
+ "after",
+ "frate",
+ "trefa"
+ ],
+ "best": [
+ "best",
+ "bets"
+ ],
+ "aeforstw": [
+ "forwaste",
+ "software"
+ ],
+ "ehnt": [
+ "hent",
+ "neth",
+ "then"
+ ],
+ "ellw": [
+ "llew",
+ "well"
+ ],
+ "eehrw": [
+ "hewer",
+ "wheer",
+ "where"
+ ],
+ "fino": [
+ "fino",
+ "foin",
+ "info"
+ ],
+ "ghirst": [
+ "girths",
+ "griths",
+ "rights"
+ ],
+ "bkoos": [
+ "bokos",
+ "books"
+ ],
+ "chloos": [
+ "cholos",
+ "school"
+ ],
+ "aceh": [
+ "ache",
+ "each",
+ "haec"
+ ],
+ "iklns": [
+ "kilns",
+ "links",
+ "slink"
+ ],
+ "ehs": [
+ "hes",
+ "she"
+ ],
+ "eeirvw": [
+ "review",
+ "viewer"
+ ],
+ "aersy": [
+ "eyras",
+ "years",
+ "reasy",
+ "resay",
+ "sayer",
+ "seary"
+ ],
+ "bkoo": [
+ "boko",
+ "book"
+ ],
+ "eimst": [
+ "emits",
+ "items",
+ "metis",
+ "mites",
+ "smite",
+ "stime",
+ "times"
+ ],
+ "acmnopy": [
+ "company",
+ "copyman"
+ ],
+ "ader": [
+ "ared",
+ "daer",
+ "dare",
+ "dear",
+ "read"
+ ],
+ "deen": [
+ "dene",
+ "eden",
+ "ende",
+ "ened",
+ "need"
+ ],
+ "amny": [
+ "many",
+ "myna"
+ ],
+ "ersu": [
+ "rues",
+ "ruse",
+ "suer",
+ "sure",
+ "user"
+ ],
+ "adis": [
+ "aids",
+ "dais",
+ "dasi",
+ "dias",
+ "disa",
+ "sadi",
+ "said",
+ "sida"
+ ],
+ "deos": [
+ "does",
+ "dose",
+ "odes"
+ ],
+ "est": [
+ "est",
+ "set"
+ ],
+ "denru": [
+ "nuder",
+ "rendu",
+ "runed",
+ "under",
+ "unred"
+ ],
+ "aeeglnr": [
+ "enlarge",
+ "general",
+ "gleaner"
+ ],
+ "aceehrrs": [
+ "reachers",
+ "rechaser",
+ "research",
+ "searcher"
+ ],
+ "ailm": [
+ "amil",
+ "amli",
+ "lima",
+ "mail",
+ "mali",
+ "mila"
+ ],
+ "amp": [
+ "amp",
+ "map",
+ "pam"
+ ],
+ "eeirsvw": [
+ "reviews",
+ "viewers"
+ ],
+ "efil": [
+ "feil",
+ "fiel",
+ "file",
+ "leif",
+ "lief",
+ "life"
+ ],
+ "know": [
+ "know",
+ "wonk"
+ ],
+ "aegms": [
+ "games",
+ "mages"
+ ],
+ "awy": [
+ "yaw",
+ "way"
+ ],
+ "adsy": [
+ "days",
+ "dyas"
+ ],
+ "aprt": [
+ "part",
+ "prat",
+ "rapt",
+ "tarp",
+ "trap"
+ ],
+ "cdlou": [
+ "cloud",
+ "could"
+ ],
+ "aegrt": [
+ "gater",
+ "grate",
+ "great",
+ "greta",
+ "retag",
+ "targe",
+ "terga"
+ ],
+ "deintu": [
+ "dunite",
+ "united",
+ "untied"
+ ],
+ "ehlot": [
+ "helot",
+ "hotel",
+ "theol",
+ "thole"
+ ],
+ "aelr": [
+ "arle",
+ "earl",
+ "eral",
+ "lare",
+ "lear",
+ "rale",
+ "real"
+ ],
+ "ceenrt": [
+ "center",
+ "centre",
+ "entrec",
+ "recent",
+ "tenrec"
+ ],
+ "abey": [
+ "abey",
+ "abye"
+ ],
+ "mstu": [
+ "must",
+ "muts",
+ "smut",
+ "stum"
+ ],
+ "eorst": [
+ "roset",
+ "rotes",
+ "rotse",
+ "soter",
+ "stero",
+ "store",
+ "tores",
+ "torse"
+ ],
+ "aelrtv": [
+ "travel",
+ "varlet"
+ ],
+ "adem": [
+ "dame",
+ "edam",
+ "emda",
+ "made",
+ "maed",
+ "mead"
+ ],
+ "eoprrt": [
+ "porret",
+ "porter",
+ "pretor",
+ "report",
+ "troper"
+ ],
+ "adeilst": [
+ "details",
+ "dilates",
+ "distale",
+ "salited"
+ ],
+ "eiln": [
+ "lien",
+ "line",
+ "neil",
+ "nile"
+ ],
+ "emrst": [
+ "mster",
+ "terms"
+ ],
+ "ehlost": [
+ "helots",
+ "hostel",
+ "hostle",
+ "hotels",
+ "tholes"
+ ],
+ "dens": [
+ "dens",
+ "ends",
+ "send",
+ "sned"
+ ],
+ "ghirt": [
+ "girth",
+ "grith",
+ "right"
+ ],
+ "abceesu": [
+ "because",
+ "besauce"
+ ],
+ "acllo": [
+ "callo",
+ "colla",
+ "local"
+ ],
+ "ehost": [
+ "ethos",
+ "shote",
+ "theos",
+ "those"
+ ],
+ "ginsu": [
+ "suing",
+ "using"
+ ],
+ "elrsstu": [
+ "lusters",
+ "lustres",
+ "results",
+ "rustles",
+ "sutlers",
+ "tussler",
+ "ulsters"
+ ],
+ "ceffio": [
+ "coiffe",
+ "office"
+ ],
+ "acdeinotu": [
+ "auctioned",
+ "cautioned",
+ "coadunite",
+ "education",
+ "noctuidae"
+ ],
+ "aailnnot": [
+ "latonian",
+ "nataloin",
+ "national"
+ ],
+ "acr": [
+ "arc",
+ "car"
+ ],
+ "degins": [
+ "deigns",
+ "design",
+ "sdeign",
+ "signed",
+ "singed"
+ ],
+ "aekt": [
+ "kate",
+ "keat",
+ "keta",
+ "take",
+ "teak"
+ ],
+ "deopst": [
+ "depots",
+ "despot",
+ "posted",
+ "stoped"
+ ],
+ "eeinnrtt": [
+ "internet",
+ "renitent",
+ "trentine"
+ ],
+ "hiintw": [
+ "inwith",
+ "whitin",
+ "within"
+ ],
+ "aesstt": [
+ "estats",
+ "states",
+ "tasset",
+ "tastes"
+ ],
+ "antw": [
+ "nawt",
+ "tawn",
+ "want"
+ ],
+ "ehnop": [
+ "pheon",
+ "phone"
+ ],
+ "deeerrsv": [
+ "deserver",
+ "reserved",
+ "reversed"
+ ],
+ "abdes": [
+ "based",
+ "beads",
+ "sabed"
+ ],
+ "cdeo": [
+ "code",
+ "coed",
+ "deco",
+ "ecod"
+ ],
+ "hosw": [
+ "hows",
+ "show"
+ ],
+ "eenv": [
+ "even",
+ "neve",
+ "veen"
+ ],
+ "aceilps": [
+ "plaices",
+ "special"
+ ],
+ "ceiprs": [
+ "crepis",
+ "cripes",
+ "persic",
+ "precis",
+ "prices",
+ "spicer"
+ ],
+ "deinx": [
+ "index",
+ "nixed"
+ ],
+ "begin": [
+ "begin",
+ "being",
+ "binge"
+ ],
+ "emnow": [
+ "menow",
+ "women"
+ ],
+ "chmu": [
+ "chum",
+ "much"
+ ],
+ "gins": [
+ "gins",
+ "sign",
+ "sing",
+ "snig"
+ ],
+ "ikln": [
+ "kiln",
+ "link"
+ ],
+ "enop": [
+ "nope",
+ "open",
+ "peon",
+ "pone"
+ ],
+ "adoty": [
+ "doaty",
+ "toady",
+ "today"
+ ],
+ "hostu": [
+ "shout",
+ "south",
+ "thous"
+ ],
+ "aces": [
+ "aces",
+ "aesc",
+ "case",
+ "esca"
+ ],
+ "aems": [
+ "asem",
+ "maes",
+ "meas",
+ "mesa",
+ "same",
+ "seam"
+ ],
+ "aegps": [
+ "gapes",
+ "pages",
+ "peags"
+ ],
+ "einorsv": [
+ "renvois",
+ "version"
+ ],
+ "ceinost": [
+ "contise",
+ "noetics",
+ "notices",
+ "section"
+ ],
+ "dfnou": [
+ "fondu",
+ "found"
+ ],
+ "oprsst": [
+ "sports",
+ "strops"
+ ],
+ "adeelrt": [
+ "alerted",
+ "altered",
+ "delater",
+ "latrede",
+ "redealt",
+ "related",
+ "treadle"
+ ],
+ "bhot": [
+ "both",
+ "thob"
+ ],
+ "aaceimnr": [
+ "amacrine",
+ "american",
+ "camarine",
+ "camerina",
+ "cinerama"
+ ],
+ "aegm": [
+ "egma",
+ "game",
+ "mage"
+ ],
+ "acer": [
+ "acer",
+ "acre",
+ "care",
+ "cera",
+ "crea",
+ "race"
+ ],
+ "alott": [
+ "lotta",
+ "total"
+ ],
+ "acelp": [
+ "capel",
+ "clape",
+ "place"
+ ],
+ "den": [
+ "den",
+ "end",
+ "ned"
+ ],
+ "addlnoow": [
+ "download",
+ "woodland"
+ ],
+ "hiottuw": [
+ "outwith",
+ "without"
+ ],
+ "epr": [
+ "per",
+ "pre",
+ "rep"
+ ],
+ "hnort": [
+ "north",
+ "thorn"
+ ],
+ "ceeorrssu": [
+ "recourses",
+ "resources"
+ ],
+ "opsst": [
+ "posts",
+ "spots",
+ "stops"
+ ],
+ "bgi": [
+ "big",
+ "gib"
+ ],
+ "adeim": [
+ "aimed",
+ "amide",
+ "damie",
+ "media"
+ ],
+ "alw": [
+ "alw",
+ "awl",
+ "law"
+ ],
+ "aertw": [
+ "tawer",
+ "water",
+ "wreat"
+ ],
+ "hiorsty": [
+ "history",
+ "toryish"
+ ],
+ "ceiprstu": [
+ "crepitus",
+ "cuprites",
+ "pictures",
+ "piecrust"
+ ],
+ "art": [
+ "art",
+ "rat",
+ "tar",
+ "tra"
+ ],
+ "ceins": [
+ "cines",
+ "senci",
+ "since"
+ ],
+ "degiu": [
+ "digue",
+ "guide"
+ ],
+ "hops": [
+ "hops",
+ "hosp",
+ "phos",
+ "posh",
+ "shop",
+ "soph"
+ ],
+ "abdor": [
+ "abord",
+ "bardo",
+ "board",
+ "broad",
+ "dobra",
+ "dorab"
+ ],
+ "acilnoot": [
+ "colation",
+ "coontail",
+ "location"
+ ],
+ "ehitw": [
+ "white",
+ "withe"
+ ],
+ "allms": [
+ "malls",
+ "small"
+ ],
+ "aginrt": [
+ "gratin",
+ "rating",
+ "taring",
+ "tringa"
+ ],
+ "aert": [
+ "aret",
+ "arte",
+ "erat",
+ "rate",
+ "tare",
+ "tear",
+ "tera"
+ ],
+ "dginru": [
+ "during",
+ "ungird",
+ "ungrid"
+ ],
+ "asu": [
+ "aus",
+ "sau",
+ "usa"
+ ],
+ "enrrtu": [
+ "return",
+ "turner"
+ ],
+ "eisst": [
+ "sesti",
+ "siest",
+ "sites",
+ "sties"
+ ],
+ "eioprsuv": [
+ "pervious",
+ "previous",
+ "viperous"
+ ],
+ "eenstv": [
+ "events",
+ "steven"
+ ],
+ "elov": [
+ "levo",
+ "love",
+ "velo",
+ "vole"
+ ],
+ "dlo": [
+ "dol",
+ "lod",
+ "old"
+ ],
+ "aimn": [
+ "amin",
+ "anim",
+ "iman",
+ "main",
+ "mani",
+ "mian",
+ "mina",
+ "naim"
+ ],
+ "cdeiinoprst": [
+ "description",
+ "discerption",
+ "predictions"
+ ],
+ "aceinnrsu": [
+ "insurance",
+ "nuisancer"
+ ],
+ "aehnort": [
+ "another",
+ "athenor",
+ "rheotan"
+ ],
+ "hwy": [
+ "hwy",
+ "why"
+ ],
+ "ahlls": [
+ "halls",
+ "shall"
+ ],
+ "illst": [
+ "lilts",
+ "still",
+ "tills"
+ ],
+ "emnoy": [
+ "emony",
+ "moyen",
+ "money"
+ ],
+ "eervy": [
+ "every",
+ "veery",
+ "verey"
+ ],
+ "giilnst": [
+ "listing",
+ "silting",
+ "sliting",
+ "tilings"
+ ],
+ "eilltt": [
+ "little",
+ "tillet"
+ ],
+ "iistv": [
+ "visit",
+ "vitis"
+ ],
+ "aesv": [
+ "aves",
+ "save",
+ "vase"
+ ],
+ "loost": [
+ "loots",
+ "lotos",
+ "sloot",
+ "sotol",
+ "stool",
+ "tools"
+ ],
+ "low": [
+ "low",
+ "lwo",
+ "owl"
+ ],
+ "elpry": [
+ "lepry",
+ "plyer",
+ "reply"
+ ],
+ "cemorstu": [
+ "costumer",
+ "customer"
+ ],
+ "acemopr": [
+ "compare",
+ "compear"
+ ],
+ "cdeilnu": [
+ "include",
+ "nuclide"
+ ],
+ "aeluv": [
+ "uveal",
+ "value"
+ ],
+ "aceilrt": [
+ "article",
+ "recital"
+ ],
+ "kory": [
+ "york",
+ "kory",
+ "roky"
+ ],
+ "amn": [
+ "man",
+ "mna",
+ "nam"
+ ],
+ "deioprv": [
+ "dropvie",
+ "prevoid",
+ "provide"
+ ],
+ "ceorsu": [
+ "cerous",
+ "course",
+ "crouse",
+ "source"
+ ],
+ "aelnr": [
+ "learn",
+ "neral",
+ "renal"
+ ],
+ "aels": [
+ "ales",
+ "elsa",
+ "lase",
+ "leas",
+ "sale",
+ "seal",
+ "slae"
+ ],
+ "adnoru": [
+ "around",
+ "arundo"
+ ],
+ "bjo": [
+ "job",
+ "obj"
+ ],
+ "ceoprss": [
+ "corpses",
+ "process"
+ ],
+ "eent": [
+ "eten",
+ "neet",
+ "nete",
+ "teen"
+ ],
+ "moor": [
+ "moor",
+ "moro",
+ "room"
+ ],
+ "oot": [
+ "oot",
+ "oto",
+ "too"
+ ],
+ "cdeirt": [
+ "credit",
+ "direct",
+ "triced"
+ ],
+ "inopt": [
+ "pinot",
+ "pinto",
+ "piton",
+ "point"
+ ],
+ "ijno": [
+ "join",
+ "joni"
+ ],
+ "aceegiorst": [
+ "categories",
+ "categorise"
+ ],
+ "estw": [
+ "stew",
+ "tews",
+ "west",
+ "wets"
+ ],
+ "aelss": [
+ "lases",
+ "sales",
+ "salse",
+ "seals"
+ ],
+ "kloo": [
+ "kolo",
+ "look"
+ ],
+ "eghilns": [
+ "english",
+ "shingle"
+ ],
+ "eflt": [
+ "felt",
+ "flet",
+ "left"
+ ],
+ "aemt": [
+ "mate",
+ "meat",
+ "meta",
+ "tame",
+ "team",
+ "tema"
+ ],
+ "aeestt": [
+ "estate",
+ "testae"
+ ],
+ "ceelst": [
+ "elects",
+ "select"
+ ],
+ "hoopst": [
+ "photos",
+ "pothos"
+ ],
+ "agy": [
+ "agy",
+ "gay"
+ ],
+ "adehrt": [
+ "dearth",
+ "hatred",
+ "rathed",
+ "thread"
+ ],
+ "acegorty": [
+ "category",
+ "greycoat"
+ ],
+ "enot": [
+ "eton",
+ "note",
+ "tone"
+ ],
+ "eilv": [
+ "evil",
+ "levi",
+ "live",
+ "veil",
+ "vile",
+ "vlei"
+ ],
+ "aeglr": [
+ "argel",
+ "argle",
+ "ergal",
+ "garle",
+ "glare",
+ "lager",
+ "large",
+ "regal"
+ ],
+ "aegllry": [
+ "allergy",
+ "gallery",
+ "largely",
+ "regally"
+ ],
+ "abelt": [
+ "ablet",
+ "batel",
+ "belat",
+ "blate",
+ "bleat",
+ "tabel",
+ "table"
+ ],
+ "eehorvw": [
+ "everwho",
+ "however",
+ "whoever"
+ ],
+ "aellry": [
+ "rallye",
+ "really"
+ ],
+ "acinot": [
+ "action",
+ "atonic",
+ "cation"
+ ],
+ "arstt": [
+ "start",
+ "tarts"
+ ],
+ "eeirss": [
+ "seiser",
+ "series",
+ "sirees"
+ ],
+ "air": [
+ "air",
+ "ira",
+ "ria"
+ ],
+ "ahmnu": [
+ "human",
+ "nahum"
+ ],
+ "esy": [
+ "yes",
+ "sey",
+ "sye"
+ ],
+ "cdenos": [
+ "codens",
+ "second"
+ ],
+ "hot": [
+ "hot",
+ "tho"
+ ],
+ "cost": [
+ "cost",
+ "cots",
+ "scot"
+ ],
+ "achmr": [
+ "charm",
+ "march"
+ ],
+ "asy": [
+ "ays",
+ "yas",
+ "say"
+ ],
+ "acdeilm": [
+ "camelid",
+ "claimed",
+ "decimal",
+ "declaim",
+ "medical"
+ ],
+ "estt": [
+ "sett",
+ "stet",
+ "test"
+ ],
+ "definr": [
+ "finder",
+ "friend",
+ "redfin",
+ "refind"
+ ],
+ "eerrsv": [
+ "revers",
+ "server",
+ "verser"
+ ],
+ "dstuy": [
+ "dusty",
+ "study"
+ ],
+ "acrt": [
+ "cart",
+ "trac"
+ ],
+ "aceilrst": [
+ "altrices",
+ "articles",
+ "recitals",
+ "selictar",
+ "sterical"
+ ],
+ "ans": [
+ "ans",
+ "san"
+ ],
+ "aagin": [
+ "again",
+ "angia"
+ ],
+ "alpy": [
+ "paly",
+ "pyal",
+ "pyla",
+ "play"
+ ],
+ "eisssu": [
+ "issues",
+ "suisse"
+ ],
+ "ailpr": [
+ "april",
+ "parli",
+ "pilar",
+ "ripal"
+ ],
+ "eenrv": [
+ "nerve",
+ "never"
+ ],
+ "erssu": [
+ "ruses",
+ "russe",
+ "suers",
+ "sures",
+ "users"
+ ],
+ "eerstt": [
+ "retest",
+ "setter",
+ "street",
+ "tester"
+ ],
+ "ciopt": [
+ "optic",
+ "picot",
+ "topic"
+ ],
+ "ghinst": [
+ "nights",
+ "snight",
+ "things"
+ ],
+ "giknorw": [
+ "kingrow",
+ "working"
+ ],
+ "aaginst": [
+ "against",
+ "antisag"
+ ],
+ "atx": [
+ "tax",
+ "xat"
+ ],
+ "enoprs": [
+ "person",
+ "speron"
+ ],
+ "below": [
+ "below",
+ "bowel",
+ "bowle",
+ "elbow"
+ ],
+ "beilmo": [
+ "bemoil",
+ "emboil",
+ "emboli",
+ "mobile"
+ ],
+ "elss": [
+ "less",
+ "sels"
+ ],
+ "got": [
+ "got",
+ "tog"
+ ],
+ "aprty": [
+ "party",
+ "trypa"
+ ],
+ "gilno": [
+ "lingo",
+ "login"
+ ],
+ "densttu": [
+ "student",
+ "stunted"
+ ],
+ "elt": [
+ "elt",
+ "let",
+ "tel"
+ ],
+ "effors": [
+ "offers",
+ "reffos"
+ ],
+ "aegll": [
+ "egall",
+ "legal"
+ ],
+ "eorsst": [
+ "retoss",
+ "rosets",
+ "sorest",
+ "sortes",
+ "stores",
+ "torses",
+ "tosser"
+ ],
+ "deis": [
+ "deis",
+ "desi",
+ "dies",
+ "ides",
+ "ised",
+ "seid",
+ "side"
+ ],
+ "act": [
+ "act",
+ "cat"
+ ],
+ "der": [
+ "der",
+ "erd",
+ "red"
+ ],
+ "acilos": [
+ "colias",
+ "scolia",
+ "social"
+ ],
+ "eoqtu": [
+ "quote",
+ "toque"
+ ],
+ "aaegglnu": [
+ "ganguela",
+ "langauge",
+ "language"
+ ],
+ "orsty": [
+ "ryots",
+ "sorty",
+ "story",
+ "stroy",
+ "tyros",
+ "troys"
+ ],
+ "ells": [
+ "ells",
+ "sell"
+ ],
+ "inoopst": [
+ "options",
+ "potions"
+ ],
+ "aerst": [
+ "arest",
+ "aster",
+ "astre",
+ "rates",
+ "reast",
+ "resat",
+ "serta",
+ "stare",
+ "strae",
+ "tares",
+ "tarse",
+ "tears",
+ "teras",
+ "treas"
+ ],
+ "aceert": [
+ "cerate",
+ "cetera",
+ "create",
+ "ecarte"
+ ],
+ "eky": [
+ "key",
+ "kye"
+ ],
+ "bdoy": [
+ "body",
+ "boyd",
+ "doby"
+ ],
+ "defil": [
+ "felid",
+ "fidel",
+ "field",
+ "filed",
+ "flied"
+ ],
+ "efw": [
+ "few",
+ "wef"
+ ],
+ "aest": [
+ "ates",
+ "east",
+ "eats",
+ "etas",
+ "sate",
+ "seat",
+ "seta",
+ "teas"
+ ],
+ "aeppr": [
+ "paper",
+ "rappe"
+ ],
+ "egilns": [
+ "glinse",
+ "ingles",
+ "lignes",
+ "seling",
+ "single",
+ "slinge"
+ ],
+ "aeg": [
+ "age",
+ "gae"
+ ],
+ "aeelmpx": [
+ "example",
+ "exempla"
+ ],
+ "aelstt": [
+ "latest",
+ "sattle",
+ "taslet"
+ ],
+ "ador": [
+ "ador",
+ "dora",
+ "orad",
+ "road"
+ ],
+ "ghint": [
+ "night",
+ "thing"
+ ],
+ "aestx": [
+ "taxes",
+ "texas"
+ ],
+ "cot": [
+ "cot",
+ "cto",
+ "oct",
+ "otc"
+ ],
+ "apy": [
+ "yap",
+ "pay",
+ "pya"
+ ],
+ "ekopr": [
+ "poker",
+ "proke"
+ ],
+ "assttu": [
+ "status",
+ "suttas"
+ ],
+ "beorsw": [
+ "bowers",
+ "bowser",
+ "browse"
+ ],
+ "eissu": [
+ "issue",
+ "susie"
+ ],
+ "aegnr": [
+ "anger",
+ "areng",
+ "grane",
+ "range",
+ "regna",
+ "renga"
+ ],
+ "eellrs": [
+ "resell",
+ "seller"
+ ],
+ "cortu": [
+ "court",
+ "crout",
+ "turco"
+ ],
+ "elrstu": [
+ "luster",
+ "lustre",
+ "result",
+ "rustle",
+ "sutler",
+ "ulster"
+ ],
+ "eirtw": [
+ "twier",
+ "twire",
+ "write"
+ ],
+ "arw": [
+ "raw",
+ "war"
+ ],
+ "nov": [
+ "nov",
+ "von"
+ ],
+ "effor": [
+ "offer",
+ "reffo"
+ ],
+ "belu": [
+ "bleu",
+ "blue",
+ "lube"
+ ],
+ "aesy": [
+ "ayes",
+ "easy",
+ "eyas",
+ "yeas"
+ ],
+ "efils": [
+ "felis",
+ "files",
+ "flies"
+ ],
+ "eeqrstu": [
+ "quester",
+ "request"
+ ],
+ "achin": [
+ "chain",
+ "chian",
+ "china"
+ ],
+ "ceiprtu": [
+ "cuprite",
+ "picture"
+ ],
+ "deens": [
+ "denes",
+ "dense",
+ "needs"
+ ],
+ "ety": [
+ "ety",
+ "yet",
+ "tye"
+ ],
+ "ajmor": [
+ "jarmo",
+ "joram",
+ "major"
+ ],
+ "arst": [
+ "arts",
+ "astr",
+ "rats",
+ "sart",
+ "star",
+ "stra",
+ "tars",
+ "tsar"
+ ],
+ "aaers": [
+ "arase",
+ "areas"
+ ],
+ "aceps": [
+ "capes",
+ "paces",
+ "scape",
+ "space"
+ ],
+ "adhn": [
+ "dhan",
+ "hand"
+ ],
+ "nsu": [
+ "nus",
+ "sun",
+ "uns"
+ ],
+ "aghinnostw": [
+ "nowanights",
+ "washington"
+ ],
+ "eegimnt": [
+ "meeting",
+ "teeming",
+ "tegmine"
+ ],
+ "eeinrstt": [
+ "insetter",
+ "interest",
+ "interset",
+ "sternite",
+ "trientes"
+ ],
+ "eekp": [
+ "keep",
+ "peek",
+ "peke"
+ ],
+ "eenrt": [
+ "enter",
+ "entre",
+ "neter",
+ "renet",
+ "rente",
+ "terne",
+ "treen"
+ ],
+ "aehrs": [
+ "asher",
+ "earsh",
+ "hares",
+ "hears",
+ "rheas",
+ "share",
+ "shear"
+ ],
+ "adegnr": [
+ "danger",
+ "gander",
+ "garden",
+ "grande",
+ "ranged"
+ ],
+ "aceimnops": [
+ "campesino",
+ "companies"
+ ],
+ "deilst": [
+ "delist",
+ "desilt",
+ "idlest",
+ "listed",
+ "silted",
+ "tildes"
+ ],
+ "abby": [
+ "abby",
+ "baby"
+ ],
+ "eegnry": [
+ "energy",
+ "gyrene",
+ "greeny",
+ "ygerne"
+ ],
+ "nru": [
+ "run",
+ "urn"
+ ],
+ "ent": [
+ "net",
+ "ten"
+ ],
+ "eiorsst": [
+ "isoster",
+ "rosiest",
+ "rossite",
+ "sorites",
+ "sorties",
+ "stories",
+ "trioses"
+ ],
+ "ptu": [
+ "put",
+ "tup"
+ ],
+ "eoprrst": [
+ "porters",
+ "pretors",
+ "reports",
+ "sporter",
+ "strepor"
+ ],
+ "rty": [
+ "tyr",
+ "try"
+ ],
+ "aegims": [
+ "ageism",
+ "images"
+ ],
+ "deeinprst": [
+ "president",
+ "serpentid"
+ ],
+ "ceinot": [
+ "conite",
+ "eciton",
+ "noetic",
+ "notice",
+ "octine"
+ ],
+ "adeh": [
+ "hade",
+ "haed",
+ "head"
+ ],
+ "adior": [
+ "aroid",
+ "doria",
+ "radio"
+ ],
+ "ilntu": [
+ "unlit",
+ "until"
+ ],
+ "cloor": [
+ "color",
+ "corol",
+ "crool"
+ ],
+ "efls": [
+ "fels",
+ "self"
+ ],
+ "cdeilnsu": [
+ "includes",
+ "nuclides",
+ "unsliced"
+ ],
+ "ceno": [
+ "cone",
+ "econ",
+ "once"
+ ],
+ "ehorst": [
+ "horste",
+ "hoster",
+ "others",
+ "reshot",
+ "throes",
+ "tosher"
+ ],
+ "aelst": [
+ "astel",
+ "least",
+ "salet",
+ "setal",
+ "slate",
+ "stale",
+ "steal",
+ "stela",
+ "taels",
+ "tales",
+ "teals",
+ "tesla"
+ ],
+ "glo": [
+ "gol",
+ "log"
+ ],
+ "definrs": [
+ "finders",
+ "friends",
+ "redfins",
+ "refinds"
+ ],
+ "afq": [
+ "faq",
+ "qaf"
+ ],
+ "adert": [
+ "adret",
+ "dater",
+ "derat",
+ "detar",
+ "drate",
+ "rated",
+ "tarde",
+ "tared",
+ "trade",
+ "tread"
+ ],
+ "deiinot": [
+ "edition",
+ "odinite",
+ "otidine",
+ "tineoid"
+ ],
+ "acrs": [
+ "arcs",
+ "cars",
+ "scar",
+ "srac"
+ ],
+ "aeegmsss": [
+ "megasses",
+ "messages"
+ ],
+ "abel": [
+ "abel",
+ "able",
+ "albe",
+ "bael",
+ "bale",
+ "beal",
+ "bela",
+ "blae",
+ "blea"
+ ],
+ "deioprsv": [
+ "disprove",
+ "provides"
+ ],
+ "aadelry": [
+ "aleyard",
+ "already"
+ ],
+ "eegnr": [
+ "genre",
+ "green",
+ "grene",
+ "neger",
+ "reneg"
+ ],
+ "deisstu": [
+ "studies",
+ "tissued"
+ ],
+ "celos": [
+ "cloes",
+ "close",
+ "coles",
+ "socle"
+ ],
+ "deirv": [
+ "deriv",
+ "diver",
+ "drive",
+ "rived",
+ "verdi"
+ ],
+ "aeelrsv": [
+ "laveers",
+ "leavers",
+ "reveals",
+ "several",
+ "vealers"
+ ],
+ "dglo": [
+ "glod",
+ "gold"
+ ],
+ "eps": [
+ "esp",
+ "pes",
+ "sep"
+ ],
+ "horst": [
+ "horst",
+ "short"
+ ],
+ "lot": [
+ "lot",
+ "tlo",
+ "tol"
+ ],
+ "aks": [
+ "ask",
+ "kas",
+ "sak"
+ ],
+ "deiilmt": [
+ "delimit",
+ "limited"
+ ],
+ "aemns": [
+ "amens",
+ "manes",
+ "manse",
+ "means",
+ "mensa",
+ "names",
+ "nemas",
+ "samen",
+ "senam"
+ ],
+ "cdeiorrt": [
+ "creditor",
+ "director"
+ ],
+ "adily": [
+ "daily",
+ "lydia"
+ ],
+ "abceh": [
+ "bache",
+ "beach"
+ ],
+ "apst": [
+ "apts",
+ "past",
+ "pats",
+ "spat",
+ "stap",
+ "taps"
+ ],
+ "nopu": [
+ "noup",
+ "puno",
+ "upon"
+ ],
+ "deiopr": [
+ "dopier",
+ "period"
+ ],
+ "aeehrtw": [
+ "weather",
+ "whereat",
+ "wreathe"
+ ],
+ "amr": [
+ "arm",
+ "mar",
+ "ram"
+ ],
+ "deno": [
+ "done",
+ "node"
+ ],
+ "accehilnt": [
+ "catchline",
+ "technical"
+ ],
+ "opr": [
+ "por",
+ "pro"
+ ],
+ "eginor": [
+ "eringo",
+ "ignore",
+ "region"
+ ],
+ "cdeorr": [
+ "corder",
+ "record"
+ ],
+ "cdeorrs": [
+ "corders",
+ "records"
+ ],
+ "aacdelnr": [
+ "calander",
+ "calandre",
+ "calendar",
+ "landrace"
+ ],
+ "cosst": [
+ "costs",
+ "scots"
+ ],
+ "aeemnsttt": [
+ "statement",
+ "testament"
+ ],
+ "aprst": [
+ "parts",
+ "prats",
+ "spart",
+ "sprat",
+ "strap",
+ "tarps",
+ "traps"
+ ],
+ "agu": [
+ "aug",
+ "gau"
+ ],
+ "eerv": [
+ "ever",
+ "reve",
+ "veer"
+ ],
+ "addlnoosw": [
+ "downloads",
+ "woodlands"
+ ],
+ "aelry": [
+ "early",
+ "layer",
+ "leary",
+ "relay"
+ ],
+ "eilms": [
+ "limes",
+ "melis",
+ "miles",
+ "slime",
+ "smile"
+ ],
+ "dnosu": [
+ "nodus",
+ "ounds",
+ "sound"
+ ],
+ "ceeorrsu": [
+ "recourse",
+ "resource"
+ ],
+ "eenprst": [
+ "penster",
+ "present",
+ "repents",
+ "serpent",
+ "strepen"
+ ],
+ "ago": [
+ "ago",
+ "goa"
+ ],
+ "dorw": [
+ "drow",
+ "word"
+ ],
+ "apr": [
+ "apr",
+ "par",
+ "rap"
+ ],
+ "einrttw": [
+ "twinter",
+ "written"
+ ],
+ "ghinost": [
+ "hosting",
+ "onsight"
+ ],
+ "elrsu": [
+ "lures",
+ "luser",
+ "rules",
+ "sluer"
+ ],
+ "afiln": [
+ "alfin",
+ "final",
+ "flain"
+ ],
+ "adltu": [
+ "adult",
+ "dault",
+ "dulat"
+ ],
+ "ceikstt": [
+ "sticket",
+ "tickets"
+ ],
+ "aiv": [
+ "iva",
+ "vai",
+ "via"
+ ],
+ "acehp": [
+ "chape",
+ "cheap",
+ "peach"
+ ],
+ "diks": [
+ "disk",
+ "kids",
+ "skid"
+ ],
+ "eimnstu": [
+ "minuets",
+ "minutes",
+ "mistune",
+ "mutines"
+ ],
+ "eels": [
+ "eels",
+ "else",
+ "lees",
+ "lese",
+ "seel",
+ "sele",
+ "slee"
+ ],
+ "ckor": [
+ "cork",
+ "rock"
+ ],
+ "adeginr": [
+ "degrain",
+ "deraign",
+ "deringa",
+ "gradine",
+ "grained",
+ "reading"
+ ],
+ "ciopst": [
+ "copist",
+ "coptis",
+ "optics",
+ "picots",
+ "postic",
+ "topics"
+ ],
+ "abd": [
+ "abd",
+ "bad",
+ "dab"
+ ],
+ "ipst": [
+ "pist",
+ "pits",
+ "spit",
+ "tips"
+ ],
+ "lpsu": [
+ "plus",
+ "puls",
+ "slup"
+ ],
+ "ceorv": [
+ "corve",
+ "cover"
+ ],
+ "deit": [
+ "deti",
+ "diet",
+ "dite",
+ "edit",
+ "tide",
+ "tied"
+ ],
+ "ceenprt": [
+ "percent",
+ "precent"
+ ],
+ "afst": [
+ "fast",
+ "fats",
+ "saft"
+ ],
+ "ceht": [
+ "chet",
+ "echt",
+ "etch",
+ "tche",
+ "tech"
+ ],
+ "eemt": [
+ "meet",
+ "mete",
+ "teem"
+ ],
+ "afr": [
+ "arf",
+ "far",
+ "fra"
+ ],
+ "aelpry": [
+ "parley",
+ "pearly",
+ "player",
+ "rapely",
+ "replay"
+ ],
+ "aegmnry": [
+ "germany",
+ "mangery"
+ ],
+ "amnotu": [
+ "amount",
+ "moutan",
+ "outman"
+ ],
+ "eefl": [
+ "feel",
+ "fele",
+ "flee",
+ "leef"
+ ],
+ "abkn": [
+ "bank",
+ "knab",
+ "nabk"
+ ],
+ "ikrs": [
+ "irks",
+ "kris",
+ "risk"
+ ],
+ "adels": [
+ "dales",
+ "deals",
+ "lades",
+ "lased",
+ "leads",
+ "slade"
+ ],
+ "aiorsuv": [
+ "saviour",
+ "various"
+ ],
+ "dorsw": [
+ "sword",
+ "words"
+ ],
+ "notw": [
+ "nowt",
+ "town",
+ "wont"
+ ],
+ "aehrt": [
+ "earth",
+ "hater",
+ "heart",
+ "herat",
+ "rathe"
+ ],
+ "cdeeeirv": [
+ "deceiver",
+ "received"
+ ],
+ "inopst": [
+ "instop",
+ "pintos",
+ "piston",
+ "pitons",
+ "points",
+ "postin"
+ ],
+ "aacemr": [
+ "acream",
+ "camera",
+ "mareca"
+ ],
+ "osty": [
+ "toys",
+ "tosy"
+ ],
+ "deeegirrst": [
+ "deregister",
+ "registered"
+ ],
+ "acelr": [
+ "carle",
+ "ceral",
+ "clare",
+ "clear",
+ "lacer"
+ ],
+ "fglo": [
+ "flog",
+ "golf"
+ ],
+ "adimno": [
+ "amidon",
+ "daimon",
+ "domain",
+ "domina"
+ ],
+ "acehprt": [
+ "chapter",
+ "patcher",
+ "repatch"
+ ],
+ "aekms": [
+ "kames",
+ "makes",
+ "samek"
+ ],
+ "deiw": [
+ "wide",
+ "wied"
+ ],
+ "aaegmnr": [
+ "gearman",
+ "manager"
+ ],
+ "iinoopst": [
+ "position",
+ "sopition"
+ ],
+ "orst": [
+ "orts",
+ "rots",
+ "sort",
+ "stor",
+ "tors"
+ ],
+ "delmos": [
+ "models",
+ "seldom",
+ "somdel"
+ ],
+ "acehilm": [
+ "michael",
+ "micheal"
+ ],
+ "acess": [
+ "cases",
+ "casse",
+ "scase"
+ ],
+ "epst": [
+ "pest",
+ "pets",
+ "sept",
+ "spet",
+ "step"
+ ],
+ "eilmps": [
+ "impels",
+ "mespil",
+ "simple"
+ ],
+ "enno": [
+ "neon",
+ "none"
+ ],
+ "eeilrssw": [
+ "weirless",
+ "wireless"
+ ],
+ "ceeilns": [
+ "license",
+ "selenic",
+ "silence"
+ ],
+ "alpu": [
+ "paul",
+ "upla"
+ ],
+ "aekl": [
+ "kale",
+ "lake",
+ "leak"
+ ],
+ "ehlow": [
+ "howel",
+ "whole"
+ ],
+ "aelrt": [
+ "alert",
+ "alter",
+ "artel",
+ "later",
+ "ratel",
+ "retal",
+ "taler",
+ "telar"
+ ],
+ "abcis": [
+ "bacis",
+ "basic"
+ ],
+ "hossw": [
+ "shows",
+ "swosh"
+ ],
+ "dehmot": [
+ "method",
+ "mothed"
+ ],
+ "cemorsstu": [
+ "costumers",
+ "customers"
+ ],
+ "eenoprss": [
+ "pessoner",
+ "response"
+ ],
+ "acceiprt": [
+ "accipter",
+ "practice"
+ ],
+ "efir": [
+ "fire",
+ "reif",
+ "rife"
+ ],
+ "adhiloy": [
+ "hyaloid",
+ "hyoidal",
+ "holiday"
+ ],
+ "acht": [
+ "cath",
+ "chat",
+ "tach"
+ ],
+ "aglno": [
+ "along",
+ "anglo",
+ "gonal",
+ "lango",
+ "logan",
+ "longa",
+ "nogal"
+ ],
+ "agmno": [
+ "among",
+ "mango",
+ "ngoma"
+ ],
+ "adeht": [
+ "death",
+ "hated"
+ ],
+ "deeps": [
+ "deeps",
+ "pedes",
+ "speed"
+ ],
+ "ceinorstu": [
+ "countries",
+ "cretinous",
+ "neurotics"
+ ],
+ "loss": [
+ "loss",
+ "sols"
+ ],
+ "acef": [
+ "cafe",
+ "face"
+ ],
+ "cdinostu": [
+ "conduits",
+ "discount",
+ "noctuids"
+ ],
+ "acdeert": [
+ "catered",
+ "cedrate",
+ "cerated",
+ "created",
+ "reacted"
+ ],
+ "bit": [
+ "bit",
+ "tib"
+ ],
+ "aceeinrs": [
+ "cerasein",
+ "increase",
+ "resiance"
+ ],
+ "adeeirstv": [
+ "advertise",
+ "derivates"
+ ],
+ "abes": [
+ "base",
+ "besa",
+ "sabe",
+ "seba"
+ ],
+ "aenr": [
+ "anre",
+ "aren",
+ "arne",
+ "earn",
+ "nare",
+ "near",
+ "rane"
+ ],
+ "ffstu": [
+ "stuff",
+ "tuffs"
+ ],
+ "aegorst": [
+ "garotes",
+ "orgeats",
+ "storage",
+ "tagsore"
+ ],
+ "dgino": [
+ "dingo",
+ "doing",
+ "gondi",
+ "gonid"
+ ],
+ "alnos": [
+ "loans",
+ "salon",
+ "sloan",
+ "solan"
+ ],
+ "ehoss": [
+ "hoses",
+ "shoes"
+ ],
+ "aenrtu": [
+ "aunter",
+ "auntre",
+ "nature"
+ ],
+ "deorrs": [
+ "dorser",
+ "orders"
+ ],
+ "nrtu": [
+ "runt",
+ "trun",
+ "turn"
+ ],
+ "enost": [
+ "notes",
+ "onset",
+ "seton",
+ "steno",
+ "stone",
+ "tenso",
+ "tones"
+ ],
+ "gikn": [
+ "gink",
+ "king"
+ ],
+ "admnoy": [
+ "amydon",
+ "dynamo",
+ "monday"
+ ],
+ "cips": [
+ "pics",
+ "spic"
+ ],
+ "aellorv": [
+ "allover",
+ "overall"
+ ],
+ "aby": [
+ "aby",
+ "bay"
+ ],
+ "eens": [
+ "ense",
+ "esne",
+ "nese",
+ "seen",
+ "snee"
+ ],
+ "aelprsy": [
+ "asperly",
+ "parleys",
+ "parsley",
+ "pyrales",
+ "players",
+ "replays",
+ "sparely",
+ "splayer"
+ ],
+ "eeginn": [
+ "engine",
+ "ingene"
+ ],
+ "oprt": [
+ "port",
+ "trop"
+ ],
+ "aegilnor": [
+ "geraniol",
+ "regional"
+ ],
+ "aderstt": [
+ "started",
+ "tetrads"
+ ],
+ "abr": [
+ "abr",
+ "arb",
+ "bar",
+ "bra",
+ "rab"
+ ],
+ "eisvw": [
+ "swive",
+ "views",
+ "wives"
+ ],
+ "bdelou": [
+ "dobule",
+ "double"
+ ],
+ "dgo": [
+ "dog",
+ "god"
+ ],
+ "ceenrs": [
+ "censer",
+ "scerne",
+ "screen",
+ "secern"
+ ],
+ "noos": [
+ "oons",
+ "soon"
+ ],
+ "eilns": [
+ "elsin",
+ "lenis",
+ "liens",
+ "lines",
+ "niels",
+ "silen",
+ "sline"
+ ],
+ "ceinnotu": [
+ "contineu",
+ "continue"
+ ],
+ "acorss": [
+ "across",
+ "oscars"
+ ],
+ "einprrt": [
+ "printer",
+ "reprint"
+ ],
+ "eru": [
+ "rue",
+ "ure"
+ ],
+ "dimn": [
+ "midn",
+ "mind"
+ ],
+ "ceeilnost": [
+ "elections",
+ "selection"
+ ],
+ "acinos": [
+ "casino",
+ "sonica"
+ ],
+ "lost": [
+ "lost",
+ "lots",
+ "slot"
+ ],
+ "ortu": [
+ "outr",
+ "rout",
+ "toru",
+ "tour"
+ ],
+ "emnu": [
+ "menu",
+ "neum"
+ ],
+ "ehop": [
+ "hope",
+ "peho"
+ ],
+ "eilrsv": [
+ "ervils",
+ "livers",
+ "livres",
+ "silver",
+ "sliver"
+ ],
+ "deiins": [
+ "deisin",
+ "indies",
+ "inside"
+ ],
+ "aemrtu": [
+ "mature",
+ "tamure"
+ ],
+ "elor": [
+ "lore",
+ "orle",
+ "role"
+ ],
+ "acem": [
+ "acme",
+ "came",
+ "mace"
+ ],
+ "aceinrt": [
+ "centiar",
+ "ceratin",
+ "certain",
+ "citrean",
+ "creatin",
+ "crinate",
+ "nacrite",
+ "nectria"
+ ],
+ "rsu": [
+ "rus",
+ "sur",
+ "urs"
+ ],
+ "elorw": [
+ "lower",
+ "owler",
+ "rowel"
+ ],
+ "mno": [
+ "mon",
+ "nom"
+ ],
+ "cmo": [
+ "com",
+ "moc"
+ ],
+ "efin": [
+ "enif",
+ "fine",
+ "neif",
+ "nife"
+ ],
+ "ags": [
+ "asg",
+ "gas",
+ "sag"
+ ],
+ "isx": [
+ "six",
+ "xis"
+ ],
+ "bhsu": [
+ "bush",
+ "hubs"
+ ],
+ "acdeiv": [
+ "advice",
+ "vedaic"
+ ],
+ "aceerr": [
+ "career",
+ "carree"
+ ],
+ "aiilmrty": [
+ "limitary",
+ "military"
+ ],
+ "aelnrt": [
+ "altern",
+ "antler",
+ "learnt",
+ "rental",
+ "ternal"
+ ],
+ "eenst": [
+ "steen",
+ "teens",
+ "tense"
+ ],
+ "ast": [
+ "ast",
+ "sat",
+ "sta",
+ "tas"
+ ],
+ "bdi": [
+ "bid",
+ "dib"
+ ],
+ "dikn": [
+ "dink",
+ "kind"
+ ],
+ "eellrss": [
+ "resells",
+ "sellers"
+ ],
+ "abcel": [
+ "cable",
+ "caleb"
+ ],
+ "aelsuv": [
+ "alveus",
+ "avulse",
+ "values"
+ ],
+ "cgimno": [
+ "coming",
+ "gnomic"
+ ],
+ "abeilns": [
+ "albines",
+ "bensail",
+ "lesbian"
+ ],
+ "acehimn": [
+ "chimane",
+ "machine"
+ ],
+ "gloo": [
+ "golo",
+ "gool",
+ "logo"
+ ],
+ "cein": [
+ "cine",
+ "nice"
+ ],
+ "ceors": [
+ "ceros",
+ "cores",
+ "corse",
+ "crose",
+ "score"
+ ],
+ "ceilnt": [
+ "client",
+ "lentic"
+ ],
+ "enrrstu": [
+ "returns",
+ "turners"
+ ],
+ "aacilpt": [
+ "capital",
+ "palatic"
+ ],
+ "aelmps": [
+ "maples",
+ "sample"
+ ],
+ "enst": [
+ "nest",
+ "nets",
+ "sent",
+ "sten",
+ "tens"
+ ],
+ "adel": [
+ "dale",
+ "deal",
+ "lade",
+ "lead",
+ "leda"
+ ],
+ "ccehio": [
+ "choice",
+ "echoic"
+ ],
+ "entw": [
+ "newt",
+ "went"
+ ],
+ "ceorssu": [
+ "courses",
+ "rescous",
+ "secours",
+ "sources",
+ "sucrose"
+ ],
+ "cemnorsu": [
+ "consumer",
+ "cornmuse",
+ "mucrones"
+ ],
+ "aioprrt": [
+ "airport",
+ "paritor"
+ ],
+ "airstt": [
+ "artist",
+ "strait",
+ "strati",
+ "traist",
+ "traits"
+ ],
+ "deiostu": [
+ "outside",
+ "tedious"
+ ],
+ "demo": [
+ "demo",
+ "dome",
+ "mode",
+ "moed"
+ ],
+ "adeis": [
+ "aides",
+ "aside",
+ "ideas",
+ "sadie"
+ ],
+ "emmrsu": [
+ "rummes",
+ "summer"
+ ],
+ "dew": [
+ "dew",
+ "wed"
+ ],
+ "eprsu": [
+ "purse",
+ "resup",
+ "sprue",
+ "super"
+ ],
+ "aelm": [
+ "alem",
+ "alme",
+ "amel",
+ "lame",
+ "leam",
+ "male",
+ "meal",
+ "mela"
+ ],
+ "aemrtt": [
+ "matter",
+ "mettar"
+ ],
+ "cmostu": [
+ "custom",
+ "muscot"
+ ],
+ "almost": [
+ "almost",
+ "smalto",
+ "stomal"
+ ],
+ "koot": [
+ "koto",
+ "toko",
+ "took"
+ ],
+ "aains": [
+ "asian",
+ "naias",
+ "sanai"
+ ],
+ "deiort": [
+ "dotier",
+ "editor",
+ "rioted",
+ "triode"
+ ],
+ "acesu": [
+ "cause",
+ "sauce"
+ ],
+ "aeilnoptt": [
+ "peltation",
+ "potential"
+ ],
+ "gnos": [
+ "nogs",
+ "snog",
+ "song"
+ ],
+ "aelt": [
+ "atle",
+ "laet",
+ "late",
+ "leat",
+ "tael",
+ "tale",
+ "teal",
+ "tela"
+ ],
+ "adei": [
+ "aide",
+ "deia",
+ "eadi",
+ "idea"
+ ],
+ "moors": [
+ "moors",
+ "rooms"
+ ],
+ "accenr": [
+ "cancer",
+ "crance"
+ ],
+ "aenors": [
+ "arseno",
+ "reason",
+ "senora"
+ ],
+ "loot": [
+ "loot",
+ "loto",
+ "tool"
+ ],
+ "aenrsw": [
+ "answer",
+ "resawn"
+ ],
+ "eopprsu": [
+ "peropus",
+ "purpose"
+ ],
+ "bde": [
+ "bde",
+ "bed",
+ "deb"
+ ],
+ "aeginoprt": [
+ "operating",
+ "pignorate"
+ ],
+ "amps": [
+ "amps",
+ "maps",
+ "pams",
+ "samp"
+ ],
+ "aginrst": [
+ "gastrin",
+ "gratins",
+ "ratings",
+ "staring"
+ ],
+ "aglss": [
+ "glass",
+ "slags"
+ ],
+ "etu": [
+ "tue",
+ "ute"
+ ],
+ "himst": [
+ "isthm",
+ "smith"
+ ],
+ "ahknt": [
+ "hankt",
+ "thank"
+ ],
+ "eeehlnopt": [
+ "phenetole",
+ "telephone"
+ ],
+ "oprst": [
+ "ports",
+ "prost",
+ "sport",
+ "sprot",
+ "strop"
+ ],
+ "adery": [
+ "deary",
+ "deray",
+ "yeard",
+ "rayed",
+ "ready"
+ ],
+ "aailmn": [
+ "almain",
+ "animal",
+ "lamina",
+ "manila"
+ ],
+ "ceersu": [
+ "cereus",
+ "ceruse",
+ "cesure",
+ "recuse",
+ "rescue",
+ "secure"
+ ],
+ "aeinooprst": [
+ "anisotrope",
+ "operations"
+ ],
+ "ilmpsy": [
+ "limpsy",
+ "simply"
+ ],
+ "achiinrst": [
+ "christian",
+ "christina",
+ "trichinas"
+ ],
+ "addennrstu": [
+ "understand",
+ "unstranded"
+ ],
+ "inoopt": [
+ "option",
+ "potion"
+ ],
+ "aemrst": [
+ "armets",
+ "martes",
+ "master",
+ "maters",
+ "matres",
+ "ramets",
+ "remast",
+ "stream",
+ "tamers"
+ ],
+ "aelnrst": [
+ "antlers",
+ "rentals",
+ "saltern",
+ "slanter",
+ "starnel",
+ "sternal"
+ ],
+ "aes": [
+ "aes",
+ "ase",
+ "sae",
+ "sea"
+ ],
+ "bdloo": [
+ "blood",
+ "boldo"
+ ],
+ "behilprsu": [
+ "publisher",
+ "republish"
+ ],
+ "aint": [
+ "aint",
+ "anti",
+ "inta",
+ "tain",
+ "tina"
+ ],
+ "aenprst": [
+ "arpents",
+ "enrapts",
+ "entraps",
+ "parents",
+ "pastern",
+ "trepans"
+ ],
+ "aikno": [
+ "ikona",
+ "konia"
+ ],
+ "acimpt": [
+ "campit",
+ "impact"
+ ],
+ "cehiknt": [
+ "kitchen",
+ "thicken"
+ ],
+ "aacilnor": [
+ "carolina",
+ "colnaria",
+ "conarial"
+ ],
+ "eeiopprrst": [
+ "peripteros",
+ "properties"
+ ],
+ "hips": [
+ "hips",
+ "phis",
+ "pish",
+ "ship"
+ ],
+ "enorsw": [
+ "owners",
+ "resown",
+ "rowens",
+ "worsen"
+ ],
+ "adeeiss": [
+ "disease",
+ "seaside"
+ ],
+ "ailty": [
+ "ality",
+ "italy",
+ "laity",
+ "taily"
+ ],
+ "ceefprt": [
+ "perfect",
+ "prefect"
+ ],
+ "ahir": [
+ "ahir",
+ "hair"
+ ],
+ "abiss": [
+ "absis",
+ "basis",
+ "bassi",
+ "isbas"
+ ],
+ "ceiist": [
+ "cities",
+ "iciest"
+ ],
+ "acdeinst": [
+ "disenact",
+ "distance"
+ ],
+ "eert": [
+ "reet",
+ "rete",
+ "teer",
+ "tree"
+ ],
+ "eeprt": [
+ "erept",
+ "peert",
+ "peter",
+ "petre"
+ ],
+ "eenrsu": [
+ "ensuer",
+ "ensure",
+ "enures",
+ "unsere"
+ ],
+ "hstu": [
+ "hust",
+ "huts",
+ "shut",
+ "thus",
+ "tush"
+ ],
+ "aertx": [
+ "extra",
+ "retax",
+ "taxer"
+ ],
+ "degisu": [
+ "guides",
+ "guised"
+ ],
+ "eiqtu": [
+ "quiet",
+ "quite"
+ ],
+ "cdeeelst": [
+ "deselect",
+ "selected"
+ ],
+ "boy": [
+ "boy",
+ "yob"
+ ],
+ "ehors": [
+ "heros",
+ "hoers",
+ "horse",
+ "shoer",
+ "shore"
+ ],
+ "eotv": [
+ "veto",
+ "voet",
+ "vote"
+ ],
+ "adforrw": [
+ "forward",
+ "froward"
+ ],
+ "eflorsw": [
+ "flowers",
+ "fowlers",
+ "reflows",
+ "wolfers"
+ ],
+ "arsst": [
+ "stars",
+ "trass",
+ "tsars"
+ ],
+ "ilsst": [
+ "lists",
+ "silts",
+ "slits"
+ ],
+ "enorw": [
+ "owner",
+ "reown",
+ "rewon",
+ "rowen"
+ ],
+ "aeilrt": [
+ "aliter",
+ "lirate",
+ "retail",
+ "retial",
+ "tailer"
+ ],
+ "aailmns": [
+ "animals",
+ "laminas",
+ "manilas"
+ ],
+ "cdeilrty": [
+ "directly",
+ "tridecyl"
+ ],
+ "aswy": [
+ "yaws",
+ "sway",
+ "ways"
+ ],
+ "nos": [
+ "nos",
+ "ons",
+ "son"
+ ],
+ "elru": [
+ "lure",
+ "rule"
+ ],
+ "acm": [
+ "cam",
+ "mac"
+ ],
+ "ghinosu": [
+ "housing",
+ "hugonis"
+ ],
+ "aekst": [
+ "keats",
+ "skate",
+ "skeat",
+ "stake",
+ "steak",
+ "takes",
+ "teaks"
+ ],
+ "gmt": [
+ "mgt",
+ "mtg"
+ ],
+ "ginrty": [
+ "tyring",
+ "trigyn",
+ "trying"
+ ],
+ "ehmort": [
+ "mother",
+ "thermo"
+ ],
+ "cddeeinors": [
+ "considered",
+ "deconsider"
+ ],
+ "dlot": [
+ "dolt",
+ "told"
+ ],
+ "inptu": [
+ "input",
+ "punti"
+ ],
+ "eeft": [
+ "feet",
+ "fete"
+ ],
+ "aegnt": [
+ "agent",
+ "etang"
+ ],
+ "bin": [
+ "bin",
+ "nib"
+ ],
+ "demnor": [
+ "modern",
+ "morned",
+ "normed",
+ "rodmen"
+ ],
+ "einors": [
+ "irones",
+ "noires",
+ "nosier",
+ "rosine",
+ "senior",
+ "soneri"
+ ],
+ "adeilnr": [
+ "ireland",
+ "lindera"
+ ],
+ "aceghint": [
+ "cheating",
+ "teaching"
+ ],
+ "door": [
+ "door",
+ "odor",
+ "oord",
+ "ordo",
+ "rood"
+ ],
+ "adgnr": [
+ "drang",
+ "grand"
+ ],
+ "eginstt": [
+ "setting",
+ "testing"
+ ],
+ "ailrt": [
+ "litra",
+ "trail",
+ "trial"
+ ],
+ "aceghr": [
+ "charge",
+ "creagh"
+ ],
+ "instu": [
+ "inust",
+ "sintu",
+ "suint",
+ "tunis",
+ "unist",
+ "units"
+ ],
+ "adeinst": [
+ "destain",
+ "detains",
+ "instead",
+ "sainted",
+ "satined",
+ "stained"
+ ],
+ "cloo": [
+ "cool",
+ "loco"
+ ],
+ "eortw": [
+ "rowet",
+ "rowte",
+ "tower",
+ "wrote"
+ ],
+ "eeinrt": [
+ "entier",
+ "entire",
+ "nerite",
+ "triene"
+ ],
+ "adegiln": [
+ "adeling",
+ "aligned",
+ "dealing",
+ "leading"
+ ],
+ "aelmt": [
+ "amlet",
+ "metal"
+ ],
+ "efinsst": [
+ "fitness",
+ "infests"
+ ],
+ "aais": [
+ "aias",
+ "asia"
+ ],
+ "essu": [
+ "sues",
+ "uses"
+ ],
+ "opttuu": [
+ "output",
+ "putout"
+ ],
+ "aeegrrt": [
+ "greater",
+ "regrate",
+ "terrage"
+ ],
+ "airsstt": [
+ "artists",
+ "straits",
+ "tsarist"
+ ],
+ "eilnoorstu": [
+ "resolution",
+ "solutioner"
+ ],
+ "eemss": [
+ "messe",
+ "seems",
+ "semes"
+ ],
+ "apss": [
+ "asps",
+ "pass",
+ "saps",
+ "spas"
+ ],
+ "aeilnorst": [
+ "lairstone",
+ "orientals",
+ "orleanist",
+ "relations",
+ "serotinal",
+ "tensorial"
+ ],
+ "rsttu": [
+ "strut",
+ "sturt",
+ "trust"
+ ],
+ "anv": [
+ "avn",
+ "nav",
+ "van"
+ ],
+ "acinnost": [
+ "actinons",
+ "canonist",
+ "contains",
+ "sanction",
+ "santonic",
+ "sonantic"
+ ],
+ "einosss": [
+ "essoins",
+ "osseins",
+ "session",
+ "sissone"
+ ],
+ "ilmtu": [
+ "multi",
+ "tumli"
+ ],
+ "aacinotv": [
+ "octavian",
+ "octavina",
+ "vacation"
+ ],
+ "ikns": [
+ "inks",
+ "kins",
+ "sink",
+ "skin"
+ ],
+ "eprv": [
+ "perv",
+ "prev"
+ ],
+ "ads": [
+ "ads",
+ "das",
+ "sad"
+ ],
+ "amry": [
+ "army",
+ "yarm",
+ "mary",
+ "myra"
+ ],
+ "cdeeeptx": [
+ "excepted",
+ "expected"
+ ],
+ "ginr": [
+ "girn",
+ "grin",
+ "ring"
+ ],
+ "adegr": [
+ "edgar",
+ "gader",
+ "garde",
+ "grade",
+ "raged"
+ ],
+ "opp": [
+ "opp",
+ "pop"
+ ],
+ "efilrt": [
+ "fertil",
+ "filter",
+ "filtre",
+ "lifter",
+ "relift",
+ "trifle"
+ ],
+ "eglnor": [
+ "longer",
+ "relong"
+ ],
+ "int": [
+ "int",
+ "nit",
+ "tin"
+ ],
+ "aelnp": [
+ "alpen",
+ "nepal",
+ "panel",
+ "penal",
+ "plane",
+ "plena"
+ ],
+ "aegmnr": [
+ "engram",
+ "german",
+ "manger",
+ "ragmen"
+ ],
+ "adefltu": [
+ "default",
+ "faulted"
+ ],
+ "eeiqrru": [
+ "querier",
+ "require"
+ ],
+ "bosy": [
+ "boys",
+ "yobs",
+ "sybo"
+ ],
+ "deep": [
+ "deep",
+ "depe",
+ "peed"
+ ],
+ "allosw": [
+ "allows",
+ "sallow",
+ "swallo"
+ ],
+ "erst": [
+ "erst",
+ "rest",
+ "rets",
+ "sert",
+ "ster",
+ "stre",
+ "tres"
+ ],
+ "einoprt": [
+ "pointer",
+ "protein",
+ "pterion",
+ "repoint",
+ "tropein",
+ "tropine"
+ ],
+ "deeoprrt": [
+ "deporter",
+ "reported"
+ ],
+ "loop": [
+ "loop",
+ "polo",
+ "pool"
+ ],
+ "ciilopst": [
+ "colpitis",
+ "politics",
+ "psilotic"
+ ],
+ "abdors": [
+ "adsorb",
+ "boards",
+ "broads",
+ "dobras"
+ ],
+ "aeiprst": [
+ "parties",
+ "pastier",
+ "piaster",
+ "piastre",
+ "pirates",
+ "raspite",
+ "spirate",
+ "tapiser",
+ "traipse"
+ ],
+ "eey": [
+ "eye",
+ "yee"
+ ],
+ "adeeelrs": [
+ "released",
+ "resealed"
+ ],
+ "aegst": [
+ "gates",
+ "geast",
+ "getas",
+ "stage"
+ ],
+ "aegrtt": [
+ "gatter",
+ "target"
+ ],
+ "ceeptx": [
+ "except",
+ "expect"
+ ],
+ "bsu": [
+ "bus",
+ "sub"
+ ],
+ "abemy": [
+ "beamy",
+ "embay",
+ "maybe"
+ ],
+ "acelps": [
+ "places",
+ "scapel"
+ ],
+ "ainps": [
+ "nipas",
+ "pains",
+ "pians",
+ "pinas",
+ "pisan",
+ "sapin",
+ "spain",
+ "spina"
+ ],
+ "cet": [
+ "cte",
+ "etc",
+ "tec"
+ ],
+ "einrtw": [
+ "twiner",
+ "winter"
+ ],
+ "eeprrssu": [
+ "perusers",
+ "pressure"
+ ],
+ "ceor": [
+ "cero",
+ "core"
+ ],
+ "abekr": [
+ "baker",
+ "brake",
+ "break",
+ "kebar"
+ ],
+ "eopprssu": [
+ "purposes",
+ "supposer"
+ ],
+ "ghhoorttuu": [
+ "outthrough",
+ "throughout"
+ ],
+ "esst": [
+ "sets",
+ "tess"
+ ],
+ "acden": [
+ "acned",
+ "caned",
+ "dance",
+ "decan"
+ ],
+ "efilst": [
+ "filets",
+ "fistle",
+ "fliest",
+ "flites",
+ "itself",
+ "stifle"
+ ],
+ "aepprs": [
+ "papers",
+ "sapper"
+ ],
+ "agilnpy": [
+ "playing",
+ "plygain"
+ ],
+ "adeerr": [
+ "dearer",
+ "reader",
+ "reared",
+ "redare",
+ "redear",
+ "reread"
+ ],
+ "ailrtuv": [
+ "virtual",
+ "vitular"
+ ],
+ "aenrssw": [
+ "answers",
+ "rawness"
+ ],
+ "enrt": [
+ "entr",
+ "rent",
+ "tern"
+ ],
+ "als": [
+ "als",
+ "las",
+ "sal",
+ "sla"
+ ],
+ "eemort": [
+ "emoter",
+ "meteor",
+ "remote"
+ ],
+ "aelpp": [
+ "appel",
+ "apple",
+ "pepla"
+ ],
+ "adegginrr": [
+ "regarding",
+ "regrading"
+ ],
+ "imn": [
+ "min",
+ "nim"
+ ],
+ "eemorv": [
+ "evermo",
+ "remove"
+ ],
+ "adi": [
+ "aid",
+ "dia",
+ "ida"
+ ],
+ "host": [
+ "host",
+ "hots",
+ "shot",
+ "soth",
+ "thos",
+ "tosh"
+ ],
+ "aceehrst": [
+ "cheaters",
+ "hectares",
+ "recheats",
+ "teachers"
+ ],
+ "bins": [
+ "bins",
+ "nibs",
+ "snib"
+ ],
+ "aalmnu": [
+ "alumna",
+ "manual"
+ ],
+ "aegnst": [
+ "agents",
+ "estang",
+ "stagne"
+ ],
+ "acdeeinrs": [
+ "ardencies",
+ "ecardines",
+ "increased"
+ ],
+ "aeiprr": [
+ "pairer",
+ "rapier",
+ "repair"
+ ],
+ "afir": [
+ "fair",
+ "fiar",
+ "raif"
+ ],
+ "eelst": [
+ "leets",
+ "leste",
+ "sleet",
+ "slete",
+ "steel",
+ "stele",
+ "teles"
+ ],
+ "defix": [
+ "defix",
+ "fixed"
+ ],
+ "gnorw": [
+ "grown",
+ "wrong"
+ ],
+ "aiprs": [
+ "pairs",
+ "paris",
+ "parsi",
+ "sarip",
+ "spair",
+ "spira"
+ ],
+ "egst": [
+ "gest",
+ "gets",
+ "steg",
+ "stge",
+ "tegs"
+ ],
+ "ceorst": [
+ "corset",
+ "cortes",
+ "coster",
+ "croset",
+ "escort",
+ "recost",
+ "rectos",
+ "scoter",
+ "sector"
+ ],
+ "eeiqrrsu": [
+ "queriers",
+ "requires"
+ ],
+ "aft": [
+ "aft",
+ "fat"
+ ],
+ "aefhrt": [
+ "father",
+ "freath",
+ "hafter",
+ "trefah"
+ ],
+ "cceeilrt": [
+ "electric",
+ "lectrice"
+ ],
+ "eoqstu": [
+ "quotes",
+ "toques"
+ ],
+ "adde": [
+ "dade",
+ "dead",
+ "deda",
+ "edda"
+ ],
+ "ceeprst": [
+ "recepts",
+ "respect",
+ "scepter",
+ "sceptre",
+ "specter",
+ "spectre"
+ ],
+ "eikm": [
+ "miek",
+ "mike"
+ ],
+ "pst": [
+ "pst",
+ "pts",
+ "spt",
+ "tps",
+ "tsp"
+ ],
+ "hortw": [
+ "rowth",
+ "throw",
+ "whort",
+ "worth",
+ "wroth"
+ ],
+ "cdeeoprrsu": [
+ "procedures",
+ "reproduces"
+ ],
+ "oopr": [
+ "poor",
+ "proo",
+ "roop"
+ ],
+ "aceehrt": [
+ "cheater",
+ "hectare",
+ "rechate",
+ "recheat",
+ "reteach",
+ "teacher"
+ ],
+ "eesy": [
+ "eyes",
+ "yees",
+ "yese"
+ ],
+ "ekorrsw": [
+ "reworks",
+ "workers"
+ ],
+ "afmr": [
+ "farm",
+ "fram"
+ ],
+ "mot": [
+ "mot",
+ "tom"
+ ],
+ "aceeirtv": [
+ "creative",
+ "reactive"
+ ],
+ "acost": [
+ "acost",
+ "actos",
+ "ascot",
+ "catso",
+ "coast",
+ "coats",
+ "costa",
+ "tacos",
+ "tacso",
+ "tasco",
+ "tosca"
+ ],
+ "aeegr": [
+ "aeger",
+ "agree",
+ "eager",
+ "eagre",
+ "ragee"
+ ],
+ "aehr": [
+ "hare",
+ "hear",
+ "hera",
+ "rhea"
+ ],
+ "aceerrs": [
+ "careers",
+ "creaser",
+ "searcer"
+ ],
+ "egos": [
+ "egos",
+ "goes",
+ "sego"
+ ],
+ "del": [
+ "del",
+ "eld",
+ "led"
+ ],
+ "afn": [
+ "fan",
+ "naf"
+ ],
+ "efmorr": [
+ "former",
+ "reform"
+ ],
+ "aeegillrs": [
+ "allergies",
+ "galleries"
+ ],
+ "dei": [
+ "dei",
+ "die",
+ "ide"
+ ],
+ "deeenprst": [
+ "presented",
+ "pretensed",
+ "repetends"
+ ],
+ "aflt": [
+ "flat",
+ "laft"
+ ],
+ "flow": [
+ "flow",
+ "fowl",
+ "wolf"
+ ],
+ "aceegins": [
+ "agencies",
+ "agenesic",
+ "genesiac"
+ ],
+ "aenprt": [
+ "arpent",
+ "enrapt",
+ "entrap",
+ "panter",
+ "parent",
+ "parten",
+ "pretan",
+ "pterna",
+ "trepan"
+ ],
+ "acghiimn": [
+ "michigan",
+ "minhagic"
+ ],
+ "acels": [
+ "alces",
+ "alecs",
+ "casel",
+ "claes",
+ "laces",
+ "scale"
+ ],
+ "adnst": [
+ "dasnt",
+ "stand"
+ ],
+ "cemnooy": [
+ "economy",
+ "monoecy"
+ ],
+ "eghhist": [
+ "eighths",
+ "heights",
+ "highest"
+ ],
+ "aefmr": [
+ "frame",
+ "fream"
+ ],
+ "aeeglns": [
+ "angeles",
+ "senegal"
+ ],
+ "ahpt": [
+ "path",
+ "phat"
+ ],
+ "aaailnrstu": [
+ "australian",
+ "saturnalia"
+ ],
+ "cefhi": [
+ "chief",
+ "fiche"
+ ],
+ "adeilt": [
+ "detail",
+ "dietal",
+ "dilate",
+ "edital",
+ "tailed"
+ ],
+ "alsw": [
+ "awls",
+ "laws",
+ "slaw"
+ ],
+ "acdeghn": [
+ "changed",
+ "ganched"
+ ],
+ "ept": [
+ "pet",
+ "pte"
+ ],
+ "adehr": [
+ "derah",
+ "hared",
+ "heard",
+ "rheda"
+ ],
+ "aceln": [
+ "ancle",
+ "canel",
+ "clean",
+ "lance",
+ "lenca"
+ ],
+ "aacfinr": [
+ "african",
+ "francia"
+ ],
+ "guy": [
+ "guy",
+ "yug"
+ ],
+ "eilstt": [
+ "stilet",
+ "titles"
+ ],
+ "aeelnrtv": [
+ "levanter",
+ "relevant",
+ "revelant"
+ ],
+ "ccennot": [
+ "concent",
+ "connect"
+ ],
+ "bbeil": [
+ "bible",
+ "blibe"
+ ],
+ "cpu": [
+ "cpu",
+ "cup"
+ ],
+ "abekst": [
+ "basket",
+ "betask"
+ ],
+ "addemn": [
+ "damned",
+ "ddname",
+ "demand",
+ "madden"
+ ],
+ "eistu": [
+ "etuis",
+ "suite"
+ ],
+ "aeinnottt": [
+ "attention",
+ "tentation"
+ ],
+ "ikps": [
+ "kips",
+ "pisk",
+ "skip",
+ "spik"
+ ],
+ "acinotu": [
+ "auction",
+ "caution"
+ ],
+ "aegr": [
+ "ager",
+ "agre",
+ "areg",
+ "gare",
+ "gear",
+ "rage"
+ ],
+ "eel": [
+ "eel",
+ "lee"
+ ],
+ "acehlrs": [
+ "charles",
+ "clasher",
+ "larches"
+ ],
+ "ainnot": [
+ "anoint",
+ "nation"
+ ],
+ "fimr": [
+ "firm",
+ "frim"
+ ],
+ "eensv": [
+ "evens",
+ "neves",
+ "seven"
+ ],
+ "delor": [
+ "lored",
+ "older"
+ ],
+ "iiillnos": [
+ "illinois",
+ "illision"
+ ],
+ "eeelmnst": [
+ "elements",
+ "steelmen"
+ ],
+ "eorrst": [
+ "resort",
+ "retros",
+ "roster",
+ "sorter",
+ "storer"
+ ],
+ "admnor": [
+ "random",
+ "rodman"
+ ],
+ "eiimnrst": [
+ "interims",
+ "minister",
+ "misinter"
+ ],
+ "kloos": [
+ "kolos",
+ "looks"
+ ],
+ "cdeiinorst": [
+ "directions",
+ "discretion",
+ "soricident"
+ ],
+ "adginrt": [
+ "darting",
+ "trading"
+ ],
+ "eforst": [
+ "fetors",
+ "forest",
+ "forset",
+ "fortes",
+ "foster",
+ "fstore",
+ "softer"
+ ],
+ "aclls": [
+ "calls",
+ "scall"
+ ],
+ "ehosw": [
+ "howes",
+ "whose"
+ ],
+ "celopu": [
+ "couple",
+ "culpeo"
+ ],
+ "deginn": [
+ "ending",
+ "ginned"
+ ],
+ "ceilnst": [
+ "clients",
+ "lentisc",
+ "scintle",
+ "stencil"
+ ],
+ "acinost": [
+ "actions",
+ "atonics",
+ "cations"
+ ],
+ "eilnst": [
+ "enlist",
+ "inlets",
+ "listen",
+ "silent",
+ "slinte",
+ "tinsel"
+ ],
+ "adekn": [
+ "danke",
+ "kande",
+ "knead",
+ "naked"
+ ],
+ "aglo": [
+ "gaol",
+ "goal",
+ "gola",
+ "olga"
+ ],
+ "dlos": [
+ "dols",
+ "olds",
+ "slod",
+ "sold"
+ ],
+ "aekmrst": [
+ "estmark",
+ "markets"
+ ],
+ "elostw": [
+ "lowest",
+ "owlets",
+ "towels"
+ ],
+ "eilsv": [
+ "elvis",
+ "evils",
+ "levis",
+ "lives",
+ "slive",
+ "veils"
+ ],
+ "aeehlrt": [
+ "haltere",
+ "leather",
+ "tarheel"
+ ],
+ "deeeimnrt": [
+ "determine",
+ "intermede"
+ ],
+ "almp": [
+ "lamp",
+ "palm"
+ ],
+ "bbo": [
+ "bob",
+ "obb"
+ ],
+ "aehpprs": [
+ "perhaps",
+ "prehaps"
+ ],
+ "aeeillstt": [
+ "satellite",
+ "telestial"
+ ],
+ "esstt": [
+ "stets",
+ "tests"
+ ],
+ "emt": [
+ "met",
+ "tem"
+ ],
+ "ainp": [
+ "nipa",
+ "pain",
+ "pani",
+ "pian",
+ "pina"
+ ],
+ "eginsstt": [
+ "settings",
+ "testings"
+ ],
+ "beruy": [
+ "buyer",
+ "rebuy"
+ ],
+ "aeilsy": [
+ "easily",
+ "elysia",
+ "sailye"
+ ],
+ "alor": [
+ "lora",
+ "oral"
+ ],
+ "dfor": [
+ "drof",
+ "ford"
+ ],
+ "eoprst": [
+ "poster",
+ "presto",
+ "repost",
+ "respot",
+ "stoper",
+ "topers",
+ "tropes"
+ ],
+ "deeg": [
+ "edge",
+ "geed"
+ ],
+ "oort": [
+ "root",
+ "roto",
+ "toro"
+ ],
+ "adhilosy": [
+ "hyaloids",
+ "holidays"
+ ],
+ "cei": [
+ "cie",
+ "ice"
+ ],
+ "eeilprs": [
+ "replies",
+ "spieler"
+ ],
+ "abell": [
+ "bella",
+ "label"
+ ],
+ "ces": [
+ "esc",
+ "sec"
+ ],
+ "cdeemmnor": [
+ "commender",
+ "recommend"
+ ],
+ "acnno": [
+ "ancon",
+ "canon"
+ ],
+ "aestw": [
+ "awest",
+ "etwas",
+ "sweat",
+ "tawse",
+ "twaes",
+ "waste"
+ ],
+ "eimntu": [
+ "minuet",
+ "minute",
+ "munite",
+ "mutine",
+ "untime"
+ ],
+ "deioprrv": [
+ "overdrip",
+ "provider"
+ ],
+ "ailnoopt": [
+ "antipolo",
+ "antipool",
+ "optional"
+ ],
+ "acdiinorty": [
+ "dictionary",
+ "indicatory"
+ ],
+ "cdlo": [
+ "clod",
+ "cold"
+ ],
+ "achir": [
+ "chair",
+ "chria"
+ ],
+ "aehps": [
+ "ephas",
+ "heaps",
+ "pesah",
+ "phase",
+ "shape"
+ ],
+ "defils": [
+ "felids",
+ "fields"
+ ],
+ "abg": [
+ "bag",
+ "gab"
+ ],
+ "eelrstt": [
+ "letters",
+ "settler",
+ "sterlet",
+ "trestle"
+ ],
+ "hirst": [
+ "hirst",
+ "shirt"
+ ],
+ "cdeinnotu": [
+ "continued",
+ "unnoticed"
+ ],
+ "ceimr": [
+ "crime",
+ "merci"
+ ],
+ "aberst": [
+ "barest",
+ "baster",
+ "bestar",
+ "breast",
+ "restab",
+ "tabers",
+ "trabes"
+ ],
+ "bim": [
+ "bim",
+ "ibm",
+ "mib"
+ ],
+ "egiilnor": [
+ "ligroine",
+ "religion",
+ "reoiling"
+ ],
+ "acilm": [
+ "claim",
+ "clima",
+ "malic"
+ ],
+ "eiimnoprss": [
+ "impression",
+ "permission"
+ ],
+ "achpt": [
+ "chapt",
+ "pacht",
+ "patch"
+ ],
+ "aeht": [
+ "ahet",
+ "eath",
+ "haet",
+ "hate",
+ "heat",
+ "thae",
+ "thea"
+ ],
+ "aeemrssu": [
+ "measures",
+ "reassume"
+ ],
+ "aeeginnort": [
+ "generation",
+ "renegation"
+ ],
+ "imss": [
+ "isms",
+ "miss",
+ "sims"
+ ],
+ "accehilm": [
+ "alchemic",
+ "chemical"
+ ],
+ "akst": [
+ "kats",
+ "skat",
+ "task"
+ ],
+ "efhilms": [
+ "flemish",
+ "himself"
+ ],
+ "nor": [
+ "nor",
+ "ron"
+ ],
+ "abeeln": [
+ "baleen",
+ "enable"
+ ],
+ "aanst": [
+ "antas",
+ "nasat",
+ "santa",
+ "satan"
+ ],
+ "dim": [
+ "dim",
+ "mid"
+ ],
+ "adeelr": [
+ "dealer",
+ "leader",
+ "redeal",
+ "relade",
+ "relead"
+ ],
+ "aeilrs": [
+ "ariels",
+ "israel",
+ "laiser",
+ "relais",
+ "resail",
+ "sailer",
+ "serail",
+ "serial"
+ ],
+ "fost": [
+ "soft",
+ "stof"
+ ],
+ "eerrssv": [
+ "servers",
+ "versers"
+ ],
+ "aelno": [
+ "alone",
+ "anole",
+ "olena"
+ ],
+ "eegimnst": [
+ "gisement",
+ "meetings"
+ ],
+ "aainorz": [
+ "arizona",
+ "azorian",
+ "zonaria"
+ ],
+ "eeinrsstt": [
+ "insetters",
+ "interests",
+ "resistent",
+ "sternites",
+ "triteness"
+ ],
+ "eflu": [
+ "flue",
+ "fuel"
+ ],
+ "aklw": [
+ "lawk",
+ "walk"
+ ],
+ "aaiilnt": [
+ "antilia",
+ "italian"
+ ],
+ "ainst": [
+ "antis",
+ "saint",
+ "sanit",
+ "satin",
+ "stain",
+ "tains"
+ ],
+ "eors": [
+ "eros",
+ "ores",
+ "roes",
+ "rose",
+ "seor",
+ "sero",
+ "sore"
+ ],
+ "aegmnrtu": [
+ "argentum",
+ "argument"
+ ],
+ "aceginrt": [
+ "antergic",
+ "argentic",
+ "catering",
+ "citrange",
+ "creating",
+ "reacting"
+ ],
+ "deioprrsv": [
+ "disprover",
+ "providers"
+ ],
+ "adegpru": [
+ "guepard",
+ "upgrade"
+ ],
+ "acfort": [
+ "factor",
+ "forcat"
+ ],
+ "aeenrst": [
+ "earnest",
+ "eastern",
+ "nearest"
+ ],
+ "acinostu": [
+ "acontius",
+ "anticous",
+ "auctions",
+ "cautions"
+ ],
+ "eeinrst": [
+ "entires",
+ "entries",
+ "trienes"
+ ],
+ "adest": [
+ "dates",
+ "sated",
+ "sedat",
+ "stade",
+ "stead",
+ "tsade"
+ ],
+ "adeeegnrt": [
+ "generated",
+ "greatened",
+ "renegated"
+ ],
+ "eimpr": [
+ "imper",
+ "prime"
+ ],
+ "iilmt": [
+ "limit",
+ "milit"
+ ],
+ "abegn": [
+ "bagne",
+ "bange",
+ "began"
+ ],
+ "epsst": [
+ "pests",
+ "septs",
+ "steps"
+ ],
+ "hopss": [
+ "phoss",
+ "shops",
+ "sophs",
+ "sposh"
+ ],
+ "defimnor": [
+ "foremind",
+ "informed"
+ ],
+ "abnru": [
+ "buran",
+ "unbar",
+ "urban"
+ ],
+ "deorst": [
+ "doters",
+ "sorted",
+ "stored",
+ "strode"
+ ],
+ "orstu": [
+ "roust",
+ "routs",
+ "rusot",
+ "stour",
+ "sutor",
+ "torus",
+ "tours"
+ ],
+ "adlo": [
+ "alod",
+ "dola",
+ "load",
+ "odal"
+ ],
+ "ablor": [
+ "balor",
+ "bolar",
+ "boral",
+ "labor",
+ "lobar"
+ ],
+ "adimn": [
+ "admin",
+ "dimna",
+ "mandi",
+ "manid"
+ ],
+ "agst": [
+ "agst",
+ "gast",
+ "gats",
+ "stag",
+ "tags"
+ ],
+ "dopr": [
+ "dorp",
+ "drop",
+ "prod"
+ ],
+ "dilos": [
+ "dilos",
+ "diols",
+ "idols",
+ "lidos",
+ "sloid",
+ "soldi",
+ "solid"
+ ],
+ "aeeinnoprstt": [
+ "penetrations",
+ "presentation"
+ ],
+ "aaegglnsu": [
+ "languages",
+ "slanguage"
+ ],
+ "abceem": [
+ "became",
+ "embace"
+ ],
+ "aegnor": [
+ "onager",
+ "orange"
+ ],
+ "eehmt": [
+ "meeth",
+ "theme"
+ ],
+ "aacgimnp": [
+ "campaign",
+ "pangamic"
+ ],
+ "aeimnr": [
+ "airmen",
+ "armine",
+ "ermani",
+ "marine",
+ "remain"
+ ],
+ "iiprst": [
+ "pitris",
+ "spirit"
+ ],
+ "acilms": [
+ "claims",
+ "miscal"
+ ],
+ "eems": [
+ "emes",
+ "mese",
+ "seem",
+ "seme",
+ "smee"
+ ],
+ "aaffirs": [
+ "affairs",
+ "raffias"
+ ],
+ "chotu": [
+ "chout",
+ "couth",
+ "thuoc",
+ "touch"
+ ],
+ "ddeeinnt": [
+ "indented",
+ "intended"
+ ],
+ "aglos": [
+ "gaols",
+ "goals"
+ ],
+ "ehir": [
+ "heir",
+ "hire"
+ ],
+ "ceeilnot": [
+ "coteline",
+ "election"
+ ],
+ "eersv": [
+ "serve",
+ "sever",
+ "veers",
+ "verse"
+ ],
+ "aenorss": [
+ "reasons",
+ "senoras"
+ ],
+ "acgim": [
+ "gamic",
+ "magic"
+ ],
+ "mnotu": [
+ "montu",
+ "mount",
+ "notum"
+ ],
+ "amrst": [
+ "marts",
+ "smart",
+ "stram",
+ "trams"
+ ],
+ "aegv": [
+ "gave",
+ "vage",
+ "vega"
+ ],
+ "enos": [
+ "enos",
+ "eons",
+ "noes",
+ "nose",
+ "ones",
+ "sone"
+ ],
+ "ailnt": [
+ "altin",
+ "latin"
+ ],
+ "cdeefiirt": [
+ "certified",
+ "rectified"
+ ],
+ "aaegmn": [
+ "agname",
+ "manage"
+ ],
+ "aknr": [
+ "karn",
+ "knar",
+ "kran",
+ "krna",
+ "nark",
+ "rank"
+ ],
+ "egnoor": [
+ "oregon",
+ "orgone",
+ "orogen"
+ ],
+ "eeelmnt": [
+ "element",
+ "leetmen",
+ "telemen"
+ ],
+ "bhirt": [
+ "birth",
+ "brith"
+ ],
+ "abesu": [
+ "abuse",
+ "beaus"
+ ],
+ "eeqrsstu": [
+ "questers",
+ "requests"
+ ],
+ "aaeeprst": [
+ "asperate",
+ "separate"
+ ],
+ "cdeeoprru": [
+ "procedure",
+ "reproduce"
+ ],
+ "adeehilprs": [
+ "dealership",
+ "leadership"
+ ],
+ "abelst": [
+ "ablest",
+ "belast",
+ "bleats",
+ "stable",
+ "tables"
+ ],
+ "deefin": [
+ "define",
+ "infeed"
+ ],
+ "acginr": [
+ "arcing",
+ "caring",
+ "racing"
+ ],
+ "gkno": [
+ "gonk",
+ "kong"
+ ],
+ "deeeloprv": [
+ "developer",
+ "redevelop"
+ ],
+ "cdeimost": [
+ "comedist",
+ "demotics",
+ "docetism",
+ "domestic"
+ ],
+ "aeimpst": [
+ "impaste",
+ "pastime"
+ ],
+ "hnoostu": [
+ "hontous",
+ "houston",
+ "nothous"
+ ],
+ "acehr": [
+ "acher",
+ "arche",
+ "chare",
+ "chera",
+ "echar",
+ "rache",
+ "reach"
+ ],
+ "aelmnt": [
+ "lament",
+ "manlet",
+ "mantel",
+ "mantle",
+ "mental"
+ ],
+ "emmnot": [
+ "moment",
+ "montem"
+ ],
+ "chin": [
+ "chin",
+ "inch"
+ ],
+ "ceenrst": [
+ "centers",
+ "centres",
+ "scenter",
+ "tenrecs"
+ ],
+ "aadegm": [
+ "dagame",
+ "damage"
+ ],
+ "abl": [
+ "abl",
+ "alb",
+ "bal",
+ "lab"
+ ],
+ "eeerrsv": [
+ "reserve",
+ "resever",
+ "reveres",
+ "reverse",
+ "severer"
+ ],
+ "ceeiprs": [
+ "piecers",
+ "pierces",
+ "precise",
+ "recipes",
+ "respice",
+ "scripee"
+ ],
+ "aagmm": [
+ "gamma",
+ "magma"
+ ],
+ "nosw": [
+ "nows",
+ "owns",
+ "snow",
+ "sown",
+ "wons"
+ ],
+ "hrttu": [
+ "thurt",
+ "truth"
+ ],
+ "cenortu": [
+ "conteur",
+ "cornute",
+ "counter",
+ "recount",
+ "trounce"
+ ],
+ "ainoort": [
+ "ontario",
+ "oration"
+ ],
+ "des": [
+ "des",
+ "eds",
+ "esd",
+ "sed"
+ ],
+ "aeimnnost": [
+ "antimeson",
+ "mannitose",
+ "minnesota",
+ "nominates"
+ ],
+ "bdegir": [
+ "begird",
+ "bridge"
+ ],
+ "aeintv": [
+ "native",
+ "navite"
+ ],
+ "denow": [
+ "endow",
+ "nowed",
+ "owned",
+ "woden"
+ ],
+ "achrt": [
+ "archt",
+ "chart",
+ "ratch"
+ ],
+ "adeerrs": [
+ "readers",
+ "redears",
+ "redsear",
+ "rereads"
+ ],
+ "aelqu": [
+ "equal",
+ "quale",
+ "queal"
+ ],
+ "adeenrtuv": [
+ "adventure",
+ "unaverted"
+ ],
+ "fioprt": [
+ "forpit",
+ "profit"
+ ],
+ "adeelrs": [
+ "dealers",
+ "leaders"
+ ],
+ "eoprsst": [
+ "posters",
+ "prestos",
+ "stopers"
+ ],
+ "aainssstt": [
+ "assistant",
+ "satanists"
+ ],
+ "aev": [
+ "ave",
+ "eva"
+ ],
+ "acdemopr": [
+ "compadre",
+ "compared"
+ ],
+ "hkooprsw": [
+ "shopwork",
+ "workshop"
+ ],
+ "egno": [
+ "geon",
+ "goen",
+ "gone"
+ ],
+ "cdeos": [
+ "codes",
+ "coeds",
+ "cosed"
+ ],
+ "dikns": [
+ "dinks",
+ "kinds"
+ ],
+ "aeelstt": [
+ "atelets",
+ "seattle",
+ "tsatlee"
+ ],
+ "aeemnssttt": [
+ "statements",
+ "testaments"
+ ],
+ "deglno": [
+ "engold",
+ "golden",
+ "longed"
+ ],
+ "aemst": [
+ "mates",
+ "meats",
+ "metas",
+ "satem",
+ "steam",
+ "stema",
+ "tames",
+ "teams"
+ ],
+ "fort": [
+ "fort",
+ "frot"
+ ],
+ "aeenst": [
+ "enates",
+ "ensate",
+ "enseat",
+ "santee",
+ "sateen",
+ "senate"
+ ],
+ "cefors": [
+ "forces",
+ "fresco"
+ ],
+ "denrtu": [
+ "deturn",
+ "dunter",
+ "retund",
+ "runted",
+ "tunder",
+ "turned"
+ ],
+ "deirt": [
+ "diter",
+ "tired",
+ "tried"
+ ],
+ "deenrrtu": [
+ "returned",
+ "unterred"
+ ],
+ "aenprtt": [
+ "pattern",
+ "reptant"
+ ],
+ "abot": [
+ "boat",
+ "bota",
+ "toba"
+ ],
+ "ademn": [
+ "admen",
+ "amend",
+ "mande",
+ "maned",
+ "menad",
+ "named"
+ ],
+ "aeehrtt": [
+ "teather",
+ "theater",
+ "theatre",
+ "thereat"
+ ],
+ "aelrs": [
+ "arles",
+ "arsle",
+ "earls",
+ "lares",
+ "laser",
+ "lears",
+ "rales",
+ "reals",
+ "seral",
+ "slare"
+ ],
+ "aeeilrr": [
+ "earlier",
+ "learier"
+ ],
+ "cino": [
+ "cion",
+ "coin",
+ "coni",
+ "icon"
+ ],
+ "aadiinn": [
+ "anidian",
+ "indiana"
+ ],
+ "cdeiinort": [
+ "cretinoid",
+ "direction"
+ ],
+ "deeelt": [
+ "delete",
+ "teedle"
+ ],
+ "acelnru": [
+ "crenula",
+ "lucarne",
+ "nuclear",
+ "unclear"
+ ],
+ "emosu": [
+ "moues",
+ "mouse"
+ ],
+ "agilns": [
+ "algins",
+ "aligns",
+ "glanis",
+ "lasing",
+ "liangs",
+ "ligans",
+ "lingas",
+ "sangil",
+ "signal"
+ ],
+ "deissu": [
+ "dissue",
+ "disuse",
+ "issued"
+ ],
+ "abinr": [
+ "abrin",
+ "bairn",
+ "brain",
+ "brian",
+ "rabin"
+ ],
+ "eflopruw": [
+ "powerful",
+ "upflower"
+ ],
+ "ademr": [
+ "armed",
+ "derma",
+ "drame",
+ "dream",
+ "madre",
+ "ramed"
+ ],
+ "aefls": [
+ "alefs",
+ "false",
+ "fleas",
+ "leafs"
+ ],
+ "acst": [
+ "acts",
+ "cast",
+ "cats",
+ "scat"
+ ],
+ "eflorw": [
+ "flower",
+ "fowler",
+ "reflow",
+ "wolfer"
+ ],
+ "adepss": [
+ "depass",
+ "passed",
+ "spades"
+ ],
+ "cip": [
+ "cpi",
+ "pci",
+ "pic"
+ ],
+ "emooprt": [
+ "promote",
+ "protome"
+ ],
+ "adestt": [
+ "stated",
+ "tasted"
+ ],
+ "aaepprs": [
+ "appears",
+ "sappare"
+ ],
+ "ceorsv": [
+ "corves",
+ "covers"
+ ],
+ "aaiimnnt": [
+ "amanitin",
+ "maintain"
+ ],
+ "imorstu": [
+ "sumitro",
+ "tourism"
+ ],
+ "amot": [
+ "atmo",
+ "atom",
+ "moat",
+ "mota",
+ "toma"
+ ],
+ "adeeimstt": [
+ "estimated",
+ "meditates"
+ ],
+ "befir": [
+ "bifer",
+ "brief",
+ "fiber",
+ "fibre"
+ ],
+ "inor": [
+ "inro",
+ "iron",
+ "noir",
+ "nori",
+ "roin"
+ ],
+ "deersv": [
+ "served",
+ "versed"
+ ],
+ "anstw": [
+ "stawn",
+ "wants",
+ "wasnt"
+ ],
+ "adeepprr": [
+ "dapperer",
+ "prepared"
+ ],
+ "diov": [
+ "ovid",
+ "void"
+ ],
+ "dgiinn": [
+ "dining",
+ "indign",
+ "niding"
+ ],
+ "aegiinnortt": [
+ "integration",
+ "orientating"
+ ],
+ "agt": [
+ "agt",
+ "gat",
+ "tag"
+ ],
+ "adeillnst": [
+ "installed",
+ "landsleit"
+ ],
+ "cdeirst": [
+ "credits",
+ "directs"
+ ],
+ "adehln": [
+ "handel",
+ "handle"
+ ],
+ "eestw": [
+ "ewest",
+ "sweet",
+ "weest",
+ "weets",
+ "weste"
+ ],
+ "deks": [
+ "desk",
+ "sked"
+ ],
+ "aceiirrt": [
+ "criteria",
+ "triceria"
+ ],
+ "adev": [
+ "dave",
+ "deva",
+ "vade",
+ "veda"
+ ],
+ "degio": [
+ "diego",
+ "dogie",
+ "geoid"
+ ],
+ "ceiv": [
+ "cive",
+ "vice"
+ ],
+ "ary": [
+ "ary",
+ "yar",
+ "ray",
+ "rya"
+ ],
+ "eeenruv": [
+ "revenue",
+ "unreeve"
+ ],
+ "aeemrsu": [
+ "measure",
+ "reamuse"
+ ],
+ "acgghinn": [
+ "changing",
+ "ganching"
+ ],
+ "eostv": [
+ "ovest",
+ "stove",
+ "votes"
+ ],
+ "aber": [
+ "bare",
+ "bear",
+ "brae"
+ ],
+ "agin": [
+ "agin",
+ "gain",
+ "inga",
+ "naig",
+ "ngai"
+ ],
+ "aceno": [
+ "acone",
+ "canoe",
+ "ocean"
+ ],
+ "ginss": [
+ "signs",
+ "sings",
+ "snigs",
+ "ssing"
+ ],
+ "ackl": [
+ "calk",
+ "kcal",
+ "lack"
+ ],
+ "degglo": [
+ "doggle",
+ "dogleg",
+ "logged"
+ ],
+ "aloppt": [
+ "applot",
+ "laptop"
+ ],
+ "aegintv": [
+ "vagient",
+ "vintage"
+ ],
+ "ainrt": [
+ "intra",
+ "riant",
+ "tairn",
+ "tarin",
+ "train",
+ "trina"
+ ],
+ "aps": [
+ "asp",
+ "pas",
+ "sap",
+ "spa"
+ ],
+ "aelnry": [
+ "anerly",
+ "layner",
+ "nearly"
+ ],
+ "aeilrty": [
+ "irately",
+ "reality",
+ "tearily"
+ ],
+ "giinor": [
+ "nigori",
+ "origin"
+ ],
+ "aggimn": [
+ "gaming",
+ "gigman"
+ ],
+ "aefrst": [
+ "afters",
+ "farset",
+ "faster",
+ "strafe"
+ ],
+ "cno": [
+ "con",
+ "nco"
+ ],
+ "psu": [
+ "pus",
+ "sup",
+ "ups"
+ ],
+ "ainnost": [
+ "anoints",
+ "nations",
+ "onanist"
+ ],
+ "eortu": [
+ "outer",
+ "outre",
+ "route",
+ "troue",
+ "utero"
+ ],
+ "mooz": [
+ "mozo",
+ "zoom"
+ ],
+ "blow": [
+ "blow",
+ "bowl"
+ ],
+ "abeltt": [
+ "batlet",
+ "battel",
+ "battle",
+ "tablet"
+ ],
+ "aeimn": [
+ "amine",
+ "anime",
+ "enami",
+ "maine",
+ "manei",
+ "manie",
+ "minae"
+ ],
+ "aekps": [
+ "peaks",
+ "sapek",
+ "spake",
+ "speak"
+ ],
+ "deiinrsstu": [
+ "disuniters",
+ "industries"
+ ],
+ "adeiilort": [
+ "editorial",
+ "radiolite"
+ ],
+ "ceehps": [
+ "cheeps",
+ "speech"
+ ],
+ "eirw": [
+ "weir",
+ "weri",
+ "wire"
+ ],
+ "alrru": [
+ "rural",
+ "urlar"
+ ],
+ "adehrs": [
+ "dasher",
+ "rhedas",
+ "shader",
+ "shared",
+ "sheard"
+ ],
+ "aept": [
+ "pate",
+ "peat",
+ "tape",
+ "teap",
+ "tepa"
+ ],
+ "cceimnoos": [
+ "economics",
+ "neocosmic"
+ ],
+ "acdi": [
+ "acid",
+ "cadi",
+ "caid"
+ ],
+ "besty": [
+ "betsy",
+ "bytes"
+ ],
+ "eghhit": [
+ "eighth",
+ "height"
+ ],
+ "aeekprs": [
+ "respeak",
+ "speaker"
+ ],
+ "abinot": [
+ "batino",
+ "bonita",
+ "oatbin",
+ "obtain"
+ ],
+ "ceffios": [
+ "coiffes",
+ "offices"
+ ],
+ "deeginrs": [
+ "designer",
+ "energids",
+ "redesign",
+ "reedings",
+ "resigned"
+ ],
+ "aadegmn": [
+ "agnamed",
+ "managed"
+ ],
+ "adefil": [
+ "afield",
+ "defail",
+ "defial",
+ "failed"
+ ],
+ "ceerst": [
+ "certes",
+ "erects",
+ "resect",
+ "screet",
+ "secret",
+ "terces"
+ ],
+ "abht": [
+ "baht",
+ "bath",
+ "bhat"
+ ],
+ "aeegintv": [
+ "agentive",
+ "negative"
+ ],
+ "adenrw": [
+ "andrew",
+ "redawn",
+ "wander",
+ "warden",
+ "warned"
+ ],
+ "efmoprr": [
+ "perform",
+ "preform"
+ ],
+ "aeeimsstt": [
+ "estimates",
+ "semistate",
+ "steamiest"
+ ],
+ "aessst": [
+ "assets",
+ "stases",
+ "tasses"
+ ],
+ "iimnrsty": [
+ "ministry",
+ "myristin"
+ ],
+ "aelrwy": [
+ "yawler",
+ "lawyer",
+ "warely"
+ ],
+ "adeimrr": [
+ "admirer",
+ "madrier",
+ "married"
+ ],
+ "aghinrs": [
+ "garnish",
+ "rashing",
+ "sharing"
+ ],
+ "aloprt": [
+ "patrol",
+ "portal",
+ "tropal"
+ ],
+ "abet": [
+ "abet",
+ "bate",
+ "beat",
+ "beta"
+ ],
+ "afil": [
+ "alif",
+ "fail",
+ "fila"
+ ],
+ "agirst": [
+ "gratis",
+ "striga"
+ ],
+ "aissst": [
+ "assist",
+ "stasis"
+ ],
+ "eginrsv": [
+ "serving",
+ "versing"
+ ],
+ "abgs": [
+ "bags",
+ "gabs"
+ ],
+ "ccimos": [
+ "comics",
+ "cosmic"
+ ],
+ "aemrstt": [
+ "matster",
+ "matters",
+ "smatter"
+ ],
+ "ehossu": [
+ "houses",
+ "shouse"
+ ],
+ "cdo": [
+ "cod",
+ "doc"
+ ],
+ "aerw": [
+ "arew",
+ "waer",
+ "ware",
+ "wear"
+ ],
+ "abegiknr": [
+ "beraking",
+ "breaking",
+ "rebaking"
+ ],
+ "aeilmttu": [
+ "mutilate",
+ "ultimate"
+ ],
+ "aelsw": [
+ "swale",
+ "sweal",
+ "wales",
+ "wasel",
+ "weals"
+ ],
+ "imnor": [
+ "minor",
+ "morin"
+ ],
+ "deeinrsst": [
+ "dissenter",
+ "residents",
+ "tiredness",
+ "triedness"
+ ],
+ "denot": [
+ "donet",
+ "noted",
+ "tendo",
+ "toned"
+ ],
+ "cddeeru": [
+ "deducer",
+ "reduced"
+ ],
+ "aerr": [
+ "rare",
+ "rear"
+ ],
+ "deefmoprr": [
+ "performed",
+ "preformed"
+ ],
+ "adisv": [
+ "davis",
+ "divas",
+ "vadis"
+ ],
+ "adeiln": [
+ "aldine",
+ "alined",
+ "daniel",
+ "delian",
+ "denial",
+ "enalid",
+ "leadin",
+ "nailed"
+ ],
+ "abrs": [
+ "arbs",
+ "bars",
+ "bras"
+ ],
+ "orw": [
+ "row",
+ "wro"
+ ],
+ "aceforst": [
+ "cofaster",
+ "forecast"
+ ],
+ "ehlps": [
+ "helps",
+ "shlep"
+ ],
+ "egilnss": [
+ "essling",
+ "singles"
+ ],
+ "amnostu": [
+ "amounts",
+ "outmans",
+ "saumont"
+ ],
+ "acinnot": [
+ "actinon",
+ "cantino",
+ "cantion",
+ "contain"
+ ],
+ "adlu": [
+ "auld",
+ "dual",
+ "laud",
+ "udal"
+ ],
+ "eirs": [
+ "eris",
+ "ires",
+ "reis",
+ "ries",
+ "rise",
+ "seri",
+ "sier",
+ "sire"
+ ],
+ "dsu": [
+ "sud",
+ "uds"
+ ],
+ "eelps": [
+ "peels",
+ "peles",
+ "sleep",
+ "speel"
+ ],
+ "bdir": [
+ "bird",
+ "brid",
+ "drib"
+ ],
+ "aceinort": [
+ "actioner",
+ "anerotic",
+ "anoretic",
+ "canotier",
+ "ceration",
+ "cortinae",
+ "creation",
+ "reaction"
+ ],
+ "acistt": [
+ "attics",
+ "static",
+ "sticta"
+ ],
+ "ceens": [
+ "cense",
+ "scene",
+ "sence"
+ ],
+ "adly": [
+ "yald",
+ "lady"
+ ],
+ "aet": [
+ "aet",
+ "ate",
+ "eat",
+ "eta",
+ "tae",
+ "tea"
+ ],
+ "acegilnn": [
+ "cleaning",
+ "enlacing"
+ ],
+ "eilnt": [
+ "inlet",
+ "intel",
+ "linet"
+ ],
+ "aegilnortu": [
+ "regulation",
+ "urogenital"
+ ],
+ "cdeinortu": [
+ "introduce",
+ "reduction"
+ ],
+ "aim": [
+ "aim",
+ "ami",
+ "ima",
+ "mia"
+ ],
+ "bdis": [
+ "bids",
+ "dibs"
+ ],
+ "deeefrrr": [
+ "deferrer",
+ "referred"
+ ],
+ "eginors": [
+ "eringos",
+ "ignores",
+ "regions",
+ "signore"
+ ],
+ "els": [
+ "els",
+ "les",
+ "sel"
+ ],
+ "acep": [
+ "cape",
+ "cepa",
+ "pace"
+ ],
+ "ann": [
+ "ann",
+ "nan"
+ ],
+ "ginrs": [
+ "girns",
+ "grins",
+ "rings"
+ ],
+ "ipt": [
+ "pit",
+ "tip",
+ "tpi"
+ ],
+ "deflnoruw": [
+ "underflow",
+ "wonderful"
+ ],
+ "eimn": [
+ "mein",
+ "mien",
+ "mine"
+ ],
+ "adeils": [
+ "aisled",
+ "deasil",
+ "ideals",
+ "ladies",
+ "sailed"
+ ],
+ "adeegr": [
+ "agreed",
+ "dragee",
+ "geared"
+ ],
+ "eeinnoprtv": [
+ "prevention",
+ "provenient"
+ ],
+ "iks": [
+ "ksi",
+ "ski"
+ ],
+ "cceors": [
+ "scorce",
+ "soccer"
+ ],
+ "imoprt": [
+ "import",
+ "promit"
+ ],
+ "ginopst": [
+ "posting",
+ "stoping"
+ ],
+ "aadeiimnnt": [
+ "diamantine",
+ "inanimated",
+ "maintained"
+ ],
+ "ccdeennot": [
+ "condecent",
+ "connected"
+ ],
+ "chirst": [
+ "christ",
+ "strich"
+ ],
+ "dgos": [
+ "dogs",
+ "gods"
+ ],
+ "cdeiorrst": [
+ "creditors",
+ "directors",
+ "recordist"
+ ],
+ "aadeh": [
+ "aahed",
+ "ahead"
+ ],
+ "mnoo": [
+ "mono",
+ "moon"
+ ],
+ "ceehms": [
+ "scheme",
+ "smeech"
+ ],
+ "deelv": [
+ "delve",
+ "devel"
+ ],
+ "eeeinprrsst": [
+ "enterprises",
+ "intersperse"
+ ],
+ "adelt": [
+ "adlet",
+ "dealt",
+ "delta",
+ "lated",
+ "taled"
+ ],
+ "aefr": [
+ "afer",
+ "fare",
+ "fear",
+ "frae",
+ "rafe"
+ ],
+ "eegikns": [
+ "seeking",
+ "skeeing"
+ ],
+ "cehins": [
+ "chines",
+ "chinse",
+ "inches",
+ "niches"
+ ],
+ "aehrss": [
+ "rashes",
+ "shares",
+ "shears"
+ ],
+ "arsw": [
+ "raws",
+ "wars"
+ ],
+ "ekpt": [
+ "kept",
+ "tpke"
+ ],
+ "aaelpp": [
+ "appale",
+ "appeal"
+ ],
+ "ceirsu": [
+ "cruise",
+ "crusie",
+ "curies"
+ ],
+ "bnosu": [
+ "bonus",
+ "bosun"
+ ],
+ "accefiiinortt": [
+ "certification",
+ "cretification",
+ "rectification"
+ ],
+ "eiloprsuvy": [
+ "perviously",
+ "previously",
+ "viperously"
+ ],
+ "ehy": [
+ "hey",
+ "hye",
+ "yeh"
+ ],
+ "aceilpss": [
+ "slipcase",
+ "specials"
+ ],
+ "deinsy": [
+ "disney",
+ "sidney"
+ ],
+ "abdeo": [
+ "abode",
+ "adobe"
+ ],
+ "deirsv": [
+ "divers",
+ "drives"
+ ],
+ "amrs": [
+ "arms",
+ "mars",
+ "rams"
+ ],
+ "eerst": [
+ "ester",
+ "estre",
+ "reest",
+ "reset",
+ "steer",
+ "stere",
+ "stree",
+ "teres",
+ "terse",
+ "trees",
+ "tsere"
+ ],
+ "agv": [
+ "avg",
+ "vag"
+ ],
+ "ahtu": [
+ "auth",
+ "haut",
+ "utah"
+ ],
+ "abenry": [
+ "barney",
+ "nearby"
+ ],
+ "mor": [
+ "mor",
+ "rom"
+ ],
+ "acdeirr": [
+ "acrider",
+ "carried"
+ ],
+ "dehi": [
+ "hide",
+ "hied"
+ ],
+ "aeginrstu": [
+ "gauntries",
+ "signature"
+ ],
+ "eefrr": [
+ "freer",
+ "frere",
+ "refer",
+ "rfree"
+ ],
+ "eillmr": [
+ "miller",
+ "remill"
+ ],
+ "acdesu": [
+ "caused",
+ "sauced"
+ ],
+ "abbes": [
+ "abbes",
+ "babes"
+ ],
+ "ddeein": [
+ "denied",
+ "indeed"
+ ],
+ "oty": [
+ "yot",
+ "toy"
+ ],
+ "deinprt": [
+ "deprint",
+ "printed"
+ ],
+ "losw": [
+ "lows",
+ "owls",
+ "slow",
+ "sowl"
+ ],
+ "aelmorv": [
+ "removal",
+ "valorem"
+ ],
+ "aeeirs": [
+ "aeries",
+ "easier"
+ ],
+ "crs": [
+ "crs",
+ "scr"
+ ],
+ "abiiillty": [
+ "alibility",
+ "liability"
+ ],
+ "hip": [
+ "hip",
+ "ihp",
+ "iph",
+ "phi"
+ ],
+ "einprrst": [
+ "printers",
+ "reprints",
+ "sprinter"
+ ],
+ "einn": [
+ "inne",
+ "nein",
+ "nine"
+ ],
+ "addgin": [
+ "adding",
+ "dading"
+ ],
+ "ceir": [
+ "cire",
+ "eric",
+ "rice"
+ ],
+ "inprst": [
+ "prints",
+ "sprint"
+ ],
+ "denps": [
+ "pends",
+ "psend",
+ "spend"
+ ],
+ "deeirsv": [
+ "derives",
+ "deviser",
+ "diverse",
+ "revised"
+ ],
+ "acilopt": [
+ "capitol",
+ "coalpit",
+ "optical",
+ "topical"
+ ],
+ "aeeilrtv": [
+ "levirate",
+ "relative"
+ ],
+ "dot": [
+ "dot",
+ "tod"
+ ],
+ "eisstu": [
+ "suites",
+ "tissue"
+ ],
+ "eefgiln": [
+ "feeling",
+ "fleeing"
+ ],
+ "eefilr": [
+ "ferlie",
+ "liefer",
+ "refile",
+ "relief"
+ ],
+ "eiinorsv": [
+ "revision",
+ "visioner"
+ ],
+ "aiort": [
+ "ariot",
+ "ratio"
+ ],
+ "adp": [
+ "adp",
+ "dap",
+ "pad"
+ ],
+ "ainr": [
+ "airn",
+ "arni",
+ "iran",
+ "nair",
+ "rain",
+ "rani"
+ ],
+ "noot": [
+ "onto",
+ "oont",
+ "toon"
+ ],
+ "aelnpt": [
+ "pantle",
+ "planet",
+ "platen"
+ ],
+ "ceeipr": [
+ "piecer",
+ "pierce",
+ "recipe"
+ ],
+ "eimprt": [
+ "permit",
+ "premit"
+ ],
+ "eegins": [
+ "genies",
+ "seeing",
+ "signee"
+ ],
+ "einnst": [
+ "innest",
+ "intens",
+ "sennit",
+ "sinnet",
+ "tennis"
+ ],
+ "abss": [
+ "bass",
+ "sabs"
+ ],
+ "bdemoor": [
+ "bedroom",
+ "boerdom",
+ "boredom",
+ "broomed"
+ ],
+ "aceinnst": [
+ "ancients",
+ "canniest",
+ "insectan",
+ "instance"
+ ],
+ "deir": [
+ "dier",
+ "dire",
+ "drie",
+ "ired",
+ "reid",
+ "ride"
+ ],
+ "cdeeilns": [
+ "declines",
+ "licensed",
+ "silenced"
+ ],
+ "adlnoor": [
+ "lardoon",
+ "orlando",
+ "rolando"
+ ],
+ "imt": [
+ "mit",
+ "tim"
+ ],
+ "eeenprrst": [
+ "presenter",
+ "repenters",
+ "represent"
+ ],
+ "aceinnoorstv": [
+ "conservation",
+ "conversation"
+ ],
+ "aipr": [
+ "pair",
+ "pari",
+ "pria",
+ "ripa"
+ ],
+ "adeil": [
+ "adiel",
+ "ailed",
+ "delia",
+ "ideal"
+ ],
+ "dno": [
+ "don",
+ "nod"
+ ],
+ "ceeips": [
+ "pieces",
+ "specie"
+ ],
+ "defhiins": [
+ "definish",
+ "fiendish",
+ "finished"
+ ],
+ "akprs": [
+ "parks",
+ "spark"
+ ],
+ "deinnr": [
+ "dinner",
+ "endrin"
+ ],
+ "acemr": [
+ "cream",
+ "macer"
+ ],
+ "nrsu": [
+ "runs",
+ "snur",
+ "urns"
+ ],
+ "aehy": [
+ "ahey",
+ "eyah",
+ "haye",
+ "yeah"
+ ],
+ "cdeiorsv": [
+ "discover",
+ "divorces"
+ ],
+ "aenprstt": [
+ "patterns",
+ "prestant",
+ "transept",
+ "trapnest"
+ ],
+ "hills": [
+ "hills",
+ "shill"
+ ],
+ "ilnosw": [
+ "lowsin",
+ "wilson"
+ ],
+ "hiirs": [
+ "irish",
+ "rishi",
+ "sirih"
+ ],
+ "aeimnrs": [
+ "marines",
+ "remains",
+ "seminar"
+ ],
+ "aeegrstt": [
+ "greatest",
+ "stratege"
+ ],
+ "eoru": [
+ "euro",
+ "roue"
+ ],
+ "ceeginr": [
+ "energic",
+ "generic"
+ ],
+ "aegsu": [
+ "agues",
+ "usage"
+ ],
+ "acp": [
+ "cap",
+ "pac"
+ ],
+ "ikn": [
+ "ink",
+ "kin"
+ ],
+ "achrst": [
+ "charts",
+ "scarth",
+ "scrath",
+ "starch"
+ ],
+ "cgiinnnotu": [
+ "continuing",
+ "unnoticing"
+ ],
+ "aekp": [
+ "keap",
+ "peak"
+ ],
+ "eistx": [
+ "exist",
+ "exits",
+ "sixte"
+ ],
+ "eehlw": [
+ "hewel",
+ "wheel"
+ ],
+ "ainrstt": [
+ "straint",
+ "transit",
+ "tristan"
+ ],
+ "accmopt": [
+ "accompt",
+ "compact"
+ ],
+ "ghilst": [
+ "lights",
+ "slight"
+ ],
+ "aegln": [
+ "agnel",
+ "angel",
+ "angle",
+ "galen",
+ "genal",
+ "glean",
+ "lagen",
+ "nagel"
+ ],
+ "eegiknp": [
+ "keeping",
+ "peeking"
+ ],
+ "aaeinopprrt": [
+ "paraprotein",
+ "preparation"
+ ],
+ "einos": [
+ "eosin",
+ "noise"
+ ],
+ "eeginns": [
+ "engines",
+ "senegin"
+ ],
+ "aaccertu": [
+ "accurate",
+ "carucate"
+ ],
+ "inp": [
+ "nip",
+ "pin"
+ ],
+ "eirsst": [
+ "resist",
+ "restis",
+ "sister",
+ "tresis"
+ ],
+ "gnu": [
+ "gnu",
+ "gun",
+ "ung"
+ ],
+ "ahprs": [
+ "harps",
+ "sharp",
+ "shrap"
+ ],
+ "abeilnss": [
+ "albiness",
+ "lesbians"
+ ],
+ "ben": [
+ "ben",
+ "neb"
+ ],
+ "aeln": [
+ "alen",
+ "elan",
+ "laen",
+ "lane",
+ "lean",
+ "lena",
+ "nael",
+ "nale",
+ "neal"
+ ],
+ "alo": [
+ "alo",
+ "lao",
+ "loa",
+ "ola"
+ ],
+ "eoprtx": [
+ "export",
+ "torpex"
+ ],
+ "eelmopry": [
+ "employer",
+ "polymere",
+ "reemploy"
+ ],
+ "knosw": [
+ "knows",
+ "snowk",
+ "swonk"
+ ],
+ "bcdeeirs": [
+ "describe",
+ "escribed"
+ ],
+ "ceiinstz": [
+ "citizens",
+ "zincites"
+ ],
+ "aelnoprss": [
+ "apronless",
+ "personals",
+ "responsal"
+ ],
+ "belortu": [
+ "boulter",
+ "trouble"
+ ],
+ "adeprs": [
+ "drapes",
+ "padres",
+ "parsed",
+ "rasped",
+ "spader",
+ "spared",
+ "spread"
+ ],
+ "accho": [
+ "chaco",
+ "choca",
+ "coach"
+ ],
+ "eiknv": [
+ "kevin",
+ "knive"
+ ],
+ "adjnor": [
+ "jardon",
+ "jordan"
+ ],
+ "aegs": [
+ "ages",
+ "gaes",
+ "sage"
+ ],
+ "glpu": [
+ "gulp",
+ "plug"
+ ],
+ "aceiilpsst": [
+ "plasticise",
+ "specialist"
+ ],
+ "giinrv": [
+ "irving",
+ "riving",
+ "virgin"
+ ],
+ "aegiiinnosttv": [
+ "investigation",
+ "tenovaginitis"
+ ],
+ "adeirs": [
+ "aiders",
+ "arised",
+ "deairs",
+ "irades",
+ "raised",
+ "redias",
+ "resaid"
+ ],
+ "aht": [
+ "aht",
+ "hat",
+ "tha"
+ ],
+ "cddeeirt": [
+ "credited",
+ "directed"
+ ],
+ "elpr": [
+ "lerp",
+ "repl"
+ ],
+ "beik": [
+ "bike",
+ "kibe"
+ ],
+ "aelpt": [
+ "leapt",
+ "lepta",
+ "palet",
+ "patel",
+ "pelta",
+ "petal",
+ "plate",
+ "pleat",
+ "tepal"
+ ],
+ "acdeiint": [
+ "actinide",
+ "ctenidia",
+ "diacetin",
+ "diactine",
+ "indicate"
+ ],
+ "bdelno": [
+ "blonde",
+ "bolden",
+ "nobled"
+ ],
+ "elos": [
+ "leos",
+ "lose",
+ "oles",
+ "sloe",
+ "sole"
+ ],
+ "eeks": [
+ "ekes",
+ "kees",
+ "seek",
+ "skee"
+ ],
+ "ablmsu": [
+ "albums",
+ "sambul",
+ "sumbal"
+ ],
+ "acehst": [
+ "chaste",
+ "cheats",
+ "sachet",
+ "scathe",
+ "scheat",
+ "taches"
+ ],
+ "egsstu": [
+ "guests",
+ "gusset"
+ ],
+ "adeeisss": [
+ "diseases",
+ "seasides"
+ ],
+ "deeeloprsv": [
+ "developers",
+ "redevelops"
+ ],
+ "noty": [
+ "yont",
+ "tony"
+ ],
+ "aadenv": [
+ "advena",
+ "nevada",
+ "vedana",
+ "venada"
+ ],
+ "ikst": [
+ "kist",
+ "kits",
+ "skit"
+ ],
+ "aadegn": [
+ "agenda",
+ "gadean"
+ ],
+ "ceinnostu": [
+ "continues",
+ "neustonic"
+ ],
+ "ackrst": [
+ "strack",
+ "tracks"
+ ],
+ "aeelmptt": [
+ "palmette",
+ "template"
+ ],
+ "ceinpr": [
+ "pincer",
+ "prince"
+ ],
+ "cceilr": [
+ "circle",
+ "cleric"
+ ],
+ "ilos": [
+ "lois",
+ "oils",
+ "silo",
+ "siol",
+ "soil",
+ "soli"
+ ],
+ "agnrst": [
+ "grants",
+ "strang"
+ ],
+ "aacilntt": [
+ "atlantic",
+ "tantalic"
+ ],
+ "etw": [
+ "tew",
+ "wet"
+ ],
+ "adderw": [
+ "edward",
+ "wadder",
+ "warded"
+ ],
+ "aegilnv": [
+ "leaving",
+ "vangeli",
+ "vealing"
+ ],
+ "denoprs": [
+ "ponders",
+ "respond"
+ ],
+ "eissz": [
+ "sizes",
+ "zeiss"
+ ],
+ "ailnp": [
+ "lapin",
+ "lipan",
+ "pinal",
+ "plain"
+ ],
+ "eksy": [
+ "keys",
+ "syke",
+ "skey",
+ "skye"
+ ],
+ "achlnu": [
+ "chulan",
+ "launch",
+ "nuchal"
+ ],
+ "ehms": [
+ "hems",
+ "mesh",
+ "shem"
+ ],
+ "blmosy": [
+ "blosmy",
+ "symbol"
+ ],
+ "aden": [
+ "aden",
+ "ande",
+ "dane",
+ "dean",
+ "edna"
+ ],
+ "epstu": [
+ "setup",
+ "spute",
+ "stupe",
+ "upset"
+ ],
+ "acfils": [
+ "califs",
+ "fiscal"
+ ],
+ "elssty": [
+ "slyest",
+ "styles"
+ ],
+ "deenrv": [
+ "denver",
+ "nerved",
+ "revend",
+ "vender"
+ ],
+ "finoty": [
+ "notify",
+ "tonify"
+ ],
+ "belsu": [
+ "blues",
+ "bulse",
+ "lubes"
+ ],
+ "ceops": [
+ "copes",
+ "copse",
+ "pecos",
+ "scope"
+ ],
+ "eilpprsu": [
+ "periplus",
+ "supplier"
+ ],
+ "adelnt": [
+ "dental",
+ "tandle"
+ ],
+ "bdeorr": [
+ "border",
+ "roberd"
+ ],
+ "acessu": [
+ "causes",
+ "causse",
+ "sauces"
+ ],
+ "adeelnr": [
+ "laender",
+ "leander",
+ "learned",
+ "reladen"
+ ],
+ "chiiorst": [
+ "historic",
+ "orchitis"
+ ],
+ "deenop": [
+ "depone",
+ "opened"
+ ],
+ "ceorss": [
+ "cessor",
+ "corses",
+ "crosse",
+ "scores",
+ "scorse"
+ ],
+ "aeilnr": [
+ "aliner",
+ "arline",
+ "enrail",
+ "lainer",
+ "lanier",
+ "larine",
+ "linear",
+ "nailer",
+ "renail"
+ ],
+ "abers": [
+ "bares",
+ "barse",
+ "baser",
+ "bears",
+ "besra",
+ "braes",
+ "saber",
+ "sabre",
+ "serab"
+ ],
+ "aejn": [
+ "jane",
+ "jean"
+ ],
+ "hop": [
+ "hop",
+ "pho",
+ "poh"
+ ],
+ "ddeeit": [
+ "dieted",
+ "edited"
+ ],
+ "aisv": [
+ "avis",
+ "siva",
+ "vias",
+ "visa"
+ ],
+ "eemrt": [
+ "meter",
+ "metre",
+ "remet",
+ "retem"
+ ],
+ "deikln": [
+ "kilned",
+ "kindle",
+ "linked"
+ ],
+ "ccenopst": [
+ "concepts",
+ "conspect"
+ ],
+ "epru": [
+ "peru",
+ "prue",
+ "pure"
+ ],
+ "deeilrv": [
+ "deliver",
+ "deviler",
+ "livered",
+ "relived",
+ "reviled",
+ "riveled"
+ ],
+ "denorw": [
+ "downer",
+ "nowder",
+ "wonder",
+ "worden"
+ ],
+ "elnosss": [
+ "lessons",
+ "sonless"
+ ],
+ "begins": [
+ "begins",
+ "beings",
+ "besing",
+ "binges"
+ ],
+ "aelrst": [
+ "alerts",
+ "alters",
+ "artels",
+ "estral",
+ "laster",
+ "lastre",
+ "rastle",
+ "ratels",
+ "relast",
+ "resalt",
+ "salter",
+ "slater",
+ "staler",
+ "stelar",
+ "talers"
+ ],
+ "adrw": [
+ "draw",
+ "ward"
+ ],
+ "aegilnrt": [
+ "alerting",
+ "altering",
+ "integral",
+ "relating",
+ "tanglier",
+ "teraglin",
+ "triangle"
+ ],
+ "aemssu": [
+ "amuses",
+ "assume",
+ "seamus"
+ ],
+ "aaceilln": [
+ "alliance",
+ "ancillae",
+ "canaille"
+ ],
+ "eehinrt": [
+ "enherit",
+ "etherin",
+ "neither",
+ "therein"
+ ],
+ "eilsw": [
+ "lewis",
+ "swile",
+ "wiles"
+ ],
+ "aeelsv": [
+ "leaves",
+ "sleave"
+ ],
+ "aceelpr": [
+ "percale",
+ "replace"
+ ],
+ "ceenorrtv": [
+ "converter",
+ "reconvert"
+ ],
+ "abbe": [
+ "abbe",
+ "babe"
+ ],
+ "agrsu": [
+ "argus",
+ "gaurs",
+ "guars",
+ "sugar"
+ ],
+ "egls": [
+ "gels",
+ "legs"
+ ],
+ "ams": [
+ "mas",
+ "sam",
+ "sma"
+ ],
+ "cikst": [
+ "stick",
+ "ticks"
+ ],
+ "aelln": [
+ "allen",
+ "ellan"
+ ],
+ "dpt": [
+ "dpt",
+ "tpd"
+ ],
+ "aeilnort": [
+ "oriental",
+ "relation",
+ "tirolean"
+ ],
+ "abdeeln": [
+ "enabled",
+ "endable"
+ ],
+ "deils": [
+ "deils",
+ "delis",
+ "idles",
+ "isled",
+ "sidle",
+ "slide"
+ ],
+ "deestt": [
+ "detest",
+ "tested"
+ ],
+ "aadeprt": [
+ "adapter",
+ "predata",
+ "readapt"
+ ],
+ "cklo": [
+ "colk",
+ "lock"
+ ],
+ "cehkoy": [
+ "chokey",
+ "hockey"
+ ],
+ "morst": [
+ "morts",
+ "storm",
+ "strom"
+ ],
+ "cimor": [
+ "micro",
+ "moric",
+ "romic"
+ ],
+ "eilm": [
+ "emil",
+ "lime",
+ "mile"
+ ],
+ "deiorst": [
+ "editors",
+ "oestrid",
+ "sortied",
+ "steroid",
+ "storied",
+ "triodes"
+ ],
+ "adehrst": [
+ "dearths",
+ "hardest",
+ "hardset",
+ "hatreds",
+ "threads",
+ "trashed"
+ ],
+ "eemprsu": [
+ "presume",
+ "supreme"
+ ],
+ "eenprsst": [
+ "pensters",
+ "pertness",
+ "presents",
+ "serpents"
+ ],
+ "efr": [
+ "erf",
+ "fer",
+ "ref"
+ ],
+ "aknt": [
+ "kant",
+ "tank"
+ ],
+ "aeeimstt": [
+ "estimate",
+ "etatisme",
+ "meatiest",
+ "teatimes"
+ ],
+ "ceiinnopst": [
+ "cispontine",
+ "inceptions",
+ "inspection"
+ ],
+ "iilmst": [
+ "limits",
+ "mislit"
+ ],
+ "aceehmnrst": [
+ "manchester",
+ "searchment"
+ ],
+ "ainpt": [
+ "inapt",
+ "paint",
+ "patin",
+ "pinta"
+ ],
+ "adely": [
+ "delay",
+ "layed",
+ "leady"
+ ],
+ "ilopt": [
+ "pilot",
+ "polit"
+ ],
+ "elottu": [
+ "outlet",
+ "tutelo"
+ ],
+ "egilnrstu": [
+ "lustering",
+ "resulting",
+ "ulstering"
+ ],
+ "anp": [
+ "nap",
+ "pan"
+ ],
+ "alrtu": [
+ "lutra",
+ "ultra"
+ ],
+ "aaeiimnnotx": [
+ "examination",
+ "exanimation"
+ ],
+ "eoprtt": [
+ "porett",
+ "potter"
+ ],
+ "alpsy": [
+ "palsy",
+ "plays",
+ "splay"
+ ],
+ "beillntu": [
+ "bulletin",
+ "unbillet"
+ ],
+ "acdeiinst": [
+ "actinides",
+ "andesitic",
+ "dianetics",
+ "indicates"
+ ],
+ "dfimoy": [
+ "domify",
+ "modify"
+ ],
+ "aadm": [
+ "adam",
+ "dama",
+ "maad"
+ ],
+ "lrtuy": [
+ "rutyl",
+ "truly"
+ ],
+ "agiinnpt": [
+ "painting",
+ "patining"
+ ],
+ "aenptt": [
+ "patent",
+ "patten",
+ "tapnet"
+ ],
+ "pps": [
+ "pps",
+ "spp"
+ ],
+ "aegint": [
+ "eating",
+ "ingate",
+ "tangie",
+ "teaing",
+ "tinage"
+ ],
+ "ceeeipprstv": [
+ "perspective",
+ "prespective"
+ ],
+ "deglo": [
+ "lodge",
+ "ogled"
+ ],
+ "egilnrst": [
+ "lingster",
+ "ringlets",
+ "sterling",
+ "tinglers"
+ ],
+ "bersuy": [
+ "buyers",
+ "rebusy"
+ ],
+ "agry": [
+ "gary",
+ "gray"
+ ],
+ "aaceglotu": [
+ "catalogue",
+ "coagulate"
+ ],
+ "aaintw": [
+ "atwain",
+ "taiwan"
+ ],
+ "cehnos": [
+ "cheson",
+ "chosen",
+ "cohens",
+ "schone"
+ ],
+ "aahrs": [
+ "asarh",
+ "haars",
+ "haras",
+ "raash",
+ "sarah"
+ ],
+ "aeilmnrt": [
+ "terminal",
+ "tramline"
+ ],
+ "aelnors": [
+ "loaners",
+ "orleans",
+ "reloans"
+ ],
+ "eeimpr": [
+ "empire",
+ "epimer",
+ "premie"
+ ],
+ "aeirs": [
+ "aesir",
+ "aries",
+ "arise",
+ "raise",
+ "serai"
+ ],
+ "aeepprr": [
+ "paperer",
+ "perpera",
+ "prepare",
+ "repaper"
+ ],
+ "aabr": [
+ "arab",
+ "arba",
+ "baar",
+ "bara"
+ ],
+ "eeimprr": [
+ "premier",
+ "reprime"
+ ],
+ "mooorrtw": [
+ "moorwort",
+ "rootworm",
+ "tomorrow",
+ "wormroot"
+ ],
+ "cddeei": [
+ "decide",
+ "deiced"
+ ],
+ "aadmr": [
+ "damar",
+ "drama"
+ ],
+ "efgimnoprr": [
+ "performing",
+ "preforming"
+ ],
+ "abeilstu": [
+ "sabulite",
+ "suitable"
+ ],
+ "abcehmr": [
+ "becharm",
+ "brecham",
+ "chamber",
+ "chambre"
+ ],
+ "aeginu": [
+ "enigua",
+ "guinea",
+ "naigue"
+ ],
+ "celmsu": [
+ "clumse",
+ "muscle"
+ ],
+ "aefginrtu": [
+ "featuring",
+ "figurante"
+ ],
+ "ios": [
+ "ios",
+ "iso",
+ "osi"
+ ],
+ "orsuy": [
+ "yours",
+ "soury"
+ ],
+ "msu": [
+ "mus",
+ "sum"
+ ],
+ "adentt": [
+ "attend",
+ "detant"
+ ],
+ "ehorsw": [
+ "reshow",
+ "shower",
+ "whores"
+ ],
+ "aaln": [
+ "alan",
+ "anal",
+ "lana"
+ ],
+ "deginns": [
+ "endings",
+ "sending"
+ ],
+ "ajnos": [
+ "janos",
+ "jason",
+ "jonas",
+ "sonja"
+ ],
+ "ghinott": [
+ "hotting",
+ "tonight"
+ ],
+ "ehlls": [
+ "hells",
+ "shell"
+ ],
+ "ako": [
+ "ako",
+ "koa",
+ "oak",
+ "oka"
+ ],
+ "atv": [
+ "tav",
+ "vat"
+ ],
+ "beer": [
+ "beer",
+ "bere",
+ "bree"
+ ],
+ "deeems": [
+ "seemed",
+ "semeed"
+ ],
+ "alors": [
+ "orals",
+ "rosal",
+ "solar",
+ "soral"
+ ],
+ "ejos": [
+ "joes",
+ "jose"
+ ],
+ "irs": [
+ "irs",
+ "sir",
+ "sri"
+ ],
+ "abelnu": [
+ "nebula",
+ "unable",
+ "unbale"
+ ],
+ "aksst": [
+ "skats",
+ "tasks"
+ ],
+ "aceeinrsst": [
+ "ancestries",
+ "resistance",
+ "senatrices"
+ ],
+ "doors": [
+ "doors",
+ "odors",
+ "ordos",
+ "roods",
+ "soord",
+ "sordo"
+ ],
+ "eorrsst": [
+ "resorts",
+ "ressort",
+ "rosters",
+ "sorters"
+ ],
+ "iiorstv": [
+ "ivorist",
+ "visitor"
+ ],
+ "intw": [
+ "twin",
+ "wint"
+ ],
+ "fhort": [
+ "forth",
+ "froth"
+ ],
+ "einrst": [
+ "estrin",
+ "inerts",
+ "insert",
+ "inters",
+ "niters",
+ "nitres",
+ "sinter",
+ "sterin",
+ "triens",
+ "trines"
+ ],
+ "abeilmort": [
+ "artmobile",
+ "baltimore"
+ ],
+ "aaegtwy": [
+ "gateway",
+ "getaway",
+ "waygate"
+ ],
+ "ailmnu": [
+ "alumin",
+ "alumni",
+ "lumina",
+ "unmail"
+ ],
+ "adginrw": [
+ "drawing",
+ "ginward",
+ "warding"
+ ],
+ "acemnor": [
+ "cremona",
+ "romance"
+ ],
+ "eimnnrsttu": [
+ "instrument",
+ "nutriments"
+ ],
+ "bceru": [
+ "bruce",
+ "cebur",
+ "cuber"
+ ],
+ "ilpst": [
+ "slipt",
+ "spilt",
+ "split"
+ ],
+ "eehmst": [
+ "smeeth",
+ "smethe",
+ "themes"
+ ],
+ "bist": [
+ "bist",
+ "bits",
+ "stib"
+ ],
+ "cdefosu": [
+ "defocus",
+ "focused"
+ ],
+ "agikns": [
+ "asking",
+ "gaskin",
+ "kiangs",
+ "kisang"
+ ],
+ "abdeeist": [
+ "beadiest",
+ "diabetes"
+ ],
+ "istu": [
+ "situ",
+ "suit",
+ "tuis"
+ ],
+ "chip": [
+ "chip",
+ "pich"
+ ],
+ "ers": [
+ "ers",
+ "res",
+ "ser"
+ ],
+ "bdeios": [
+ "bodies",
+ "dobies",
+ "obside"
+ ],
+ "imnos": [
+ "minos",
+ "osmin",
+ "simon"
+ ],
+ "eirrstw": [
+ "wrister",
+ "writers"
+ ],
+ "delov": [
+ "loved",
+ "voled"
+ ],
+ "bdirs": [
+ "birds",
+ "dribs"
+ ],
+ "eeenprrsst": [
+ "presenters",
+ "represents"
+ ],
+ "achr": [
+ "arch",
+ "char",
+ "rach"
+ ],
+ "adesv": [
+ "devas",
+ "saved"
+ ],
+ "acnoort": [
+ "cartoon",
+ "coranto"
+ ],
+ "hosst": [
+ "hosts",
+ "shots",
+ "soths",
+ "stosh"
+ ],
+ "emoor": [
+ "moore",
+ "romeo"
+ ],
+ "adegnrt": [
+ "dragnet",
+ "granted"
+ ],
+ "ccehios": [
+ "choices",
+ "secchio"
+ ],
+ "abcnor": [
+ "bracon",
+ "carbon",
+ "corban"
+ ],
+ "egiilnnst": [
+ "enlisting",
+ "listening",
+ "tinseling"
+ ],
+ "kloootu": [
+ "lookout",
+ "outlook"
+ ],
+ "aeimssv": [
+ "massive",
+ "mavises"
+ ],
+ "aertt": [
+ "atter",
+ "tarte",
+ "tater",
+ "teart",
+ "tetra",
+ "treat"
+ ],
+ "adeehr": [
+ "adhere",
+ "header",
+ "hedera",
+ "rehead",
+ "rhedae"
+ ],
+ "defmor": [
+ "deform",
+ "formed"
+ ],
+ "dgir": [
+ "gird",
+ "grid"
+ ],
+ "eehsst": [
+ "sheets",
+ "theses"
+ ],
+ "acikprt": [
+ "patrick",
+ "tripack"
+ ],
+ "eoprtu": [
+ "pouter",
+ "puerto",
+ "roupet",
+ "troupe",
+ "uptore"
+ ],
+ "aalmps": [
+ "lampas",
+ "plasma"
+ ],
+ "aeginnrs": [
+ "aginners",
+ "earnings",
+ "engrains",
+ "grannies"
+ ],
+ "ikrss": [
+ "kriss",
+ "risks"
+ ],
+ "acehrrt": [
+ "charter",
+ "ratcher",
+ "rechart"
+ ],
+ "fgi": [
+ "fig",
+ "gif"
+ ],
+ "aaabbrr": [
+ "barabra",
+ "barbara"
+ ],
+ "ademrs": [
+ "dermas",
+ "dreams",
+ "madres"
+ ],
+ "begglor": [
+ "boggler",
+ "broggle"
+ ],
+ "cegiilnns": [
+ "licensing",
+ "silencing"
+ ],
+ "aceht": [
+ "cheat",
+ "tache",
+ "teach",
+ "theca"
+ ],
+ "adipr": [
+ "adrip",
+ "padri",
+ "pardi",
+ "rapid"
+ ],
+ "deiopst": [
+ "deposit",
+ "dopiest",
+ "podites",
+ "posited",
+ "sopited",
+ "topside"
+ ],
+ "aailnt": [
+ "antlia",
+ "latian",
+ "nalita"
+ ],
+ "aans": [
+ "anas",
+ "ansa",
+ "nasa",
+ "saan"
+ ],
+ "eehlsw": [
+ "shewel",
+ "wheels"
+ ],
+ "aeelmpstt": [
+ "palmettes",
+ "templates"
+ ],
+ "afmorst": [
+ "farmost",
+ "formats"
+ ],
+ "abt": [
+ "abt",
+ "bat",
+ "tab"
+ ],
+ "ddeenps": [
+ "depends",
+ "despend"
+ ],
+ "boost": [
+ "boost",
+ "boots"
+ ],
+ "eorrtu": [
+ "retour",
+ "roture",
+ "router",
+ "tourer"
+ ],
+ "degiint": [
+ "dieting",
+ "editing",
+ "ignited"
+ ],
+ "deflor": [
+ "folder",
+ "refold"
+ ],
+ "elpsu": [
+ "lepus",
+ "pules",
+ "pulse"
+ ],
+ "corstu": [
+ "courts",
+ "scrout",
+ "scruto"
+ ],
+ "deiortt": [
+ "detroit",
+ "dottier"
+ ],
+ "emort": [
+ "metro",
+ "moter"
+ ],
+ "iprst": [
+ "spirt",
+ "sprit",
+ "stirp",
+ "strip",
+ "trips"
+ ],
+ "aelpr": [
+ "lepra",
+ "paler",
+ "parel",
+ "parle",
+ "pearl",
+ "perla",
+ "prela",
+ "relap"
+ ],
+ "deeinrst": [
+ "disenter",
+ "indesert",
+ "inserted",
+ "resident",
+ "sentried",
+ "sintered"
+ ],
+ "lopt": [
+ "plot",
+ "polt"
+ ],
+ "adegrr": [
+ "darger",
+ "garred",
+ "gerard",
+ "grader",
+ "redrag",
+ "regard"
+ ],
+ "eisstx": [
+ "exists",
+ "sexist",
+ "sixtes"
+ ],
+ "eikrst": [
+ "kiters",
+ "skiter",
+ "strike"
+ ],
+ "aehrtt": [
+ "hatter",
+ "threat"
+ ],
+ "ehinoprsw": [
+ "ownership",
+ "shipowner"
+ ],
+ "aeilrrt": [
+ "retiral",
+ "retrial",
+ "trailer"
+ ],
+ "aenn": [
+ "anne",
+ "nane"
+ ],
+ "acelst": [
+ "castle",
+ "cleats",
+ "eclats",
+ "scalet",
+ "sclate"
+ ],
+ "adegnrs": [
+ "dangers",
+ "ganders",
+ "gardens"
+ ],
+ "deimss": [
+ "deisms",
+ "demiss",
+ "dismes",
+ "missed"
+ ],
+ "aeinqtu": [
+ "antique",
+ "quinate"
+ ],
+ "bio": [
+ "bio",
+ "ibo",
+ "obi"
+ ],
+ "acgint": [
+ "acting",
+ "cating"
+ ],
+ "adehs": [
+ "ashed",
+ "deash",
+ "hades",
+ "heads",
+ "sadhe",
+ "shade"
+ ],
+ "aemx": [
+ "amex",
+ "exam",
+ "xema"
+ ],
+ "gloos": [
+ "gools",
+ "logos"
+ ],
+ "aeinqstu": [
+ "antiques",
+ "quanties"
+ ],
+ "deinsty": [
+ "density",
+ "destiny"
+ ],
+ "anry": [
+ "yarn",
+ "nary"
+ ],
+ "aegnrst": [
+ "angster",
+ "argents",
+ "garnets",
+ "nagster",
+ "strange"
+ ],
+ "bdes": [
+ "beds",
+ "debs"
+ ],
+ "cps": [
+ "cps",
+ "csp"
+ ],
+ "eelmoprsy": [
+ "employers",
+ "reemploys"
+ ],
+ "egry": [
+ "gery",
+ "gyre",
+ "grey"
+ ],
+ "addeemn": [
+ "amended",
+ "deadmen"
+ ],
+ "bdlo": [
+ "bold",
+ "dobl"
+ ],
+ "elnoss": [
+ "lesson",
+ "nossel",
+ "onless"
+ ],
+ "aceimn": [
+ "anemic",
+ "cinema",
+ "iceman"
+ ],
+ "aesst": [
+ "asset",
+ "easts",
+ "sates",
+ "seats",
+ "tasse"
+ ],
+ "acns": [
+ "cans",
+ "scan"
+ ],
+ "eeersv": [
+ "everse",
+ "reeves",
+ "severe"
+ ],
+ "aeeegnrt": [
+ "generate",
+ "renegate",
+ "teenager"
+ ],
+ "aeilnssst": [
+ "saintless",
+ "saltiness",
+ "slatiness",
+ "stainless"
+ ],
+ "ahilopsst": [
+ "hospitals",
+ "thalposis"
+ ],
+ "hmoru": [
+ "humor",
+ "mohur"
+ ],
+ "adeg": [
+ "aged",
+ "egad",
+ "gade",
+ "gaed"
+ ],
+ "ceeinoptx": [
+ "exception",
+ "expection"
+ ],
+ "deilv": [
+ "devil",
+ "divel",
+ "lived"
+ ],
+ "adinortu": [
+ "duration",
+ "unadroit"
+ ],
+ "cis": [
+ "cis",
+ "csi",
+ "sci",
+ "sic"
+ ],
+ "det": [
+ "det",
+ "ted"
+ ],
+ "adimnos": [
+ "daimons",
+ "domains",
+ "madison"
+ ],
+ "fgilny": [
+ "flying",
+ "flingy"
+ ],
+ "ins": [
+ "ins",
+ "isn",
+ "nis",
+ "sin"
+ ],
+ "adeginorz": [
+ "dragonize",
+ "organized"
+ ],
+ "aapr": [
+ "apar",
+ "paar",
+ "para"
+ ],
+ "eeimnss": [
+ "nemesis",
+ "siemens"
+ ],
+ "aemnt": [
+ "ament",
+ "manet",
+ "meant",
+ "menat",
+ "menta",
+ "teman"
+ ],
+ "aceprtu": [
+ "capture",
+ "cuprate",
+ "uptrace"
+ ],
+ "dnopsu": [
+ "pondus",
+ "pounds"
+ ],
+ "dees": [
+ "dees",
+ "seed"
+ ],
+ "deeirs": [
+ "desire",
+ "eiders",
+ "reside"
+ ],
+ "eemst": [
+ "meets",
+ "metes",
+ "steem",
+ "teems",
+ "temse"
+ ],
+ "eepr": [
+ "peer",
+ "pere",
+ "pree"
+ ],
+ "adekmr": [
+ "demark",
+ "marked"
+ ],
+ "deinrv": [
+ "driven",
+ "nervid",
+ "verdin"
+ ],
+ "adeemrsu": [
+ "madurese",
+ "measured"
+ ],
+ "adhnostu": [
+ "handouts",
+ "thousand"
+ ],
+ "acegr": [
+ "cager",
+ "garce",
+ "grace"
+ ],
+ "anssu": [
+ "nasus",
+ "susan"
+ ],
+ "gin": [
+ "gin",
+ "ign",
+ "ing",
+ "nig"
+ ],
+ "aadms": [
+ "adams",
+ "damas"
+ ],
+ "hnopty": [
+ "phyton",
+ "python",
+ "typhon"
+ ],
+ "emnorst": [
+ "mentors",
+ "monster"
+ ],
+ "aelx": [
+ "alex",
+ "axel",
+ "axle",
+ "exla"
+ ],
+ "beno": [
+ "beno",
+ "bone",
+ "ebon"
+ ],
+ "bgsu": [
+ "bugs",
+ "subg"
+ ],
+ "einnr": [
+ "inner",
+ "renin"
+ ],
+ "ailorttu": [
+ "outtrail",
+ "tutorial"
+ ],
+ "dem": [
+ "dem",
+ "med"
+ ],
+ "eeeginnrs": [
+ "engineers",
+ "geneserin"
+ ],
+ "eintty": [
+ "entity",
+ "tinety"
+ ],
+ "ceirssu": [
+ "ciruses",
+ "cruises"
+ ],
+ "aegt": [
+ "aget",
+ "gaet",
+ "gate",
+ "geat",
+ "geta",
+ "tega"
+ ],
+ "amnor": [
+ "manor",
+ "moran",
+ "norma",
+ "ramon",
+ "roman"
+ ],
+ "deistu": [
+ "duties",
+ "eudist",
+ "setuid",
+ "suited"
+ ],
+ "cehist": [
+ "ethics",
+ "itches",
+ "sethic"
+ ],
+ "adgnor": [
+ "dragon",
+ "gardon",
+ "grando"
+ ],
+ "bsuy": [
+ "buys",
+ "busy"
+ ],
+ "aacinpt": [
+ "capitan",
+ "captain",
+ "panctia"
+ ],
+ "aeghint": [
+ "gahnite",
+ "heating"
+ ],
+ "egl": [
+ "gel",
+ "leg"
+ ],
+ "abc": [
+ "abc",
+ "bac",
+ "cab"
+ ],
+ "ailr": [
+ "aril",
+ "lair",
+ "lari",
+ "liar",
+ "lira",
+ "rail",
+ "rial"
+ ],
+ "abeillr": [
+ "braille",
+ "liberal"
+ ],
+ "betu": [
+ "bute",
+ "tebu",
+ "tube"
+ ],
+ "nrstu": [
+ "runts",
+ "snurt",
+ "turns"
+ ],
+ "acceh": [
+ "cache",
+ "chace"
+ ],
+ "belt": [
+ "belt",
+ "blet"
+ ],
+ "aaiimnnot": [
+ "amination",
+ "animation"
+ ],
+ "acelor": [
+ "carole",
+ "coaler",
+ "coelar",
+ "colera",
+ "oracle",
+ "recoal"
+ ],
+ "aeels": [
+ "easel",
+ "lease"
+ ],
+ "adeirsst": [
+ "diasters",
+ "disaster",
+ "disrates"
+ ],
+ "celnoos": [
+ "colones",
+ "console"
+ ],
+ "agint": [
+ "ating",
+ "giant",
+ "tangi",
+ "tiang"
+ ],
+ "aalmr": [
+ "alarm",
+ "malar",
+ "maral",
+ "marla",
+ "ramal"
+ ],
+ "alsuu": [
+ "luaus",
+ "usual"
+ ],
+ "adgilno": [
+ "angloid",
+ "digonal",
+ "loading"
+ ],
+ "bor": [
+ "bor",
+ "bro",
+ "orb",
+ "rob"
+ ],
+ "deeginrss": [
+ "designers",
+ "redesigns"
+ ],
+ "orstw": [
+ "strow",
+ "trows",
+ "worst",
+ "worts"
+ ],
+ "aaeginnrt": [
+ "argentina",
+ "tanagrine"
+ ],
+ "ceiimmnoorss": [
+ "commissioner",
+ "recommission"
+ ],
+ "agmnor": [
+ "gorman",
+ "morgan"
+ ],
+ "aeelprsu": [
+ "pleasure",
+ "serpulae"
+ ],
+ "deeinort": [
+ "enteroid",
+ "orendite",
+ "oriented"
+ ],
+ "aeegl": [
+ "aegle",
+ "aglee",
+ "eagle",
+ "galee"
+ ],
+ "enrsu": [
+ "nurse",
+ "resun",
+ "runes"
+ ],
+ "aeprry": [
+ "prayer",
+ "repray"
+ ],
+ "acehinrru": [
+ "hurricane",
+ "raunchier"
+ ],
+ "aegopst": [
+ "gestapo",
+ "postage",
+ "potages"
+ ],
+ "cdeoprru": [
+ "procured",
+ "producer"
+ ],
+ "adil": [
+ "dail",
+ "dali",
+ "dial",
+ "laid",
+ "lida"
+ ],
+ "aekmr": [
+ "maker",
+ "marek",
+ "merak"
+ ],
+ "cikps": [
+ "picks",
+ "spick"
+ ],
+ "efhist": [
+ "fetish",
+ "fishet"
+ ],
+ "acinoss": [
+ "caisson",
+ "casinos",
+ "cassino",
+ "cassoni"
+ ],
+ "ekmos": [
+ "mokes",
+ "smoke"
+ ],
+ "aacehp": [
+ "achape",
+ "apache"
+ ],
+ "efilrst": [
+ "filters",
+ "frislet",
+ "lifters",
+ "slifter",
+ "stifler",
+ "trifles"
+ ],
+ "acdeinooprrt": [
+ "adrenotropic",
+ "incorporated"
+ ],
+ "acfrt": [
+ "craft",
+ "fract"
+ ],
+ "aaprt": [
+ "apart",
+ "trapa"
+ ],
+ "eglnou": [
+ "longue",
+ "lounge"
+ ],
+ "adm": [
+ "adm",
+ "dam",
+ "mad"
+ ],
+ "aghilmort": [
+ "algorithm",
+ "logarithm"
+ ],
+ "eims": [
+ "mise",
+ "semi",
+ "sime"
+ ],
+ "cinos": [
+ "cions",
+ "coins",
+ "cosin",
+ "icons",
+ "oscin",
+ "scion",
+ "sonic"
+ ],
+ "glnorsty": [
+ "strongyl",
+ "strongly"
+ ],
+ "aeeilnntv": [
+ "levantine",
+ "valentine"
+ ],
+ "ekn": [
+ "ken",
+ "nek"
+ ],
+ "einoprst": [
+ "pointers",
+ "proteins",
+ "ripstone",
+ "tropines"
+ ],
+ "aabcelp": [
+ "capable",
+ "pacable"
+ ],
+ "illt": [
+ "itll",
+ "lilt",
+ "till"
+ ],
+ "enp": [
+ "nep",
+ "pen"
+ ],
+ "enops": [
+ "opens",
+ "peons",
+ "pones"
+ ],
+ "ehos": [
+ "hoes",
+ "hose",
+ "shoe"
+ ],
+ "adns": [
+ "ands",
+ "sand"
+ ],
+ "deiinost": [
+ "desition",
+ "editions",
+ "sedition"
+ ],
+ "ailmny": [
+ "amylin",
+ "mainly"
+ ],
+ "anr": [
+ "arn",
+ "nar",
+ "ran"
+ ],
+ "aaeilmnprt": [
+ "palermitan",
+ "parliament"
+ ],
+ "acort": [
+ "actor",
+ "carot",
+ "coart",
+ "corta",
+ "croat",
+ "rocta",
+ "taroc",
+ "troca"
+ ],
+ "aacdellot": [
+ "acetaldol",
+ "allocated"
+ ],
+ "ceiintz": [
+ "citizen",
+ "zincite"
+ ],
+ "ccorsu": [
+ "crocus",
+ "occurs",
+ "succor"
+ ],
+ "aeimnty": [
+ "amenity",
+ "anytime"
+ ],
+ "eils": [
+ "isle",
+ "leis",
+ "lies",
+ "lise",
+ "sile"
+ ],
+ "alotuy": [
+ "layout",
+ "lutayo",
+ "outlay"
+ ],
+ "bls": [
+ "bls",
+ "lbs"
+ ],
+ "aly": [
+ "aly",
+ "lay"
+ ],
+ "ehorss": [
+ "horses",
+ "shoers",
+ "shores"
+ ],
+ "aenwy": [
+ "wayne",
+ "waney"
+ ],
+ "adenot": [
+ "atoned",
+ "donate"
+ ],
+ "ekorrw": [
+ "rework",
+ "worker"
+ ],
+ "aeilv": [
+ "alive",
+ "avile"
+ ],
+ "eelmpt": [
+ "pelmet",
+ "temple"
+ ],
+ "ginsw": [
+ "swing",
+ "wings"
+ ],
+ "abekrs": [
+ "bakers",
+ "basker",
+ "brakes",
+ "breaks",
+ "kebars"
+ ],
+ "aerstw": [
+ "rawest",
+ "tawers",
+ "waster",
+ "waters"
+ ],
+ "eimoprs": [
+ "imposer",
+ "promise",
+ "semipro"
+ ],
+ "hint": [
+ "hint",
+ "thin"
+ ],
+ "degir": [
+ "dirge",
+ "egrid",
+ "gride",
+ "redig",
+ "ridge"
+ ],
+ "ahirrs": [
+ "arrish",
+ "harris",
+ "rarish",
+ "shirra",
+ "sirrah"
+ ],
+ "aciloprt": [
+ "plicator",
+ "tropical"
+ ],
+ "eersstt": [
+ "retests",
+ "setters",
+ "streets",
+ "tersest",
+ "testers"
+ ],
+ "ceortv": [
+ "corvet",
+ "covert",
+ "vector"
+ ],
+ "beffru": [
+ "buffer",
+ "rebuff"
+ ],
+ "elppru": [
+ "pulper",
+ "purple"
+ ],
+ "def": [
+ "def",
+ "fed"
+ ],
+ "adeiinnosstt": [
+ "destinations",
+ "dissentation"
+ ],
+ "elst": [
+ "lest",
+ "lets",
+ "selt"
+ ],
+ "almtuu": [
+ "mutual",
+ "umlaut"
+ ],
+ "inoprs": [
+ "orpins",
+ "prinos",
+ "prison",
+ "spinor"
+ ],
+ "iklls": [
+ "kills",
+ "skill"
+ ],
+ "achirs": [
+ "chairs",
+ "ischar",
+ "rachis"
+ ],
+ "denrt": [
+ "drent",
+ "trend"
+ ],
+ "aeirrs": [
+ "airers",
+ "ariser",
+ "raiser",
+ "serrai",
+ "sierra"
+ ],
+ "deerst": [
+ "desert",
+ "deters",
+ "rested"
+ ],
+ "delost": [
+ "oldest",
+ "sloted",
+ "stoled"
+ ],
+ "abn": [
+ "abn",
+ "ban",
+ "nab"
+ ],
+ "abdhknoo": [
+ "bandhook",
+ "handbook"
+ ],
+ "aaegintv": [
+ "navigate",
+ "vaginate"
+ ],
+ "eorsw": [
+ "owser",
+ "resow",
+ "serow",
+ "sower",
+ "swore",
+ "worse"
+ ],
+ "immstu": [
+ "mutism",
+ "summit"
+ ],
+ "aep": [
+ "ape",
+ "epa",
+ "pea"
+ ],
+ "acepss": [
+ "scapes",
+ "spaces"
+ ],
+ "aceeps": [
+ "escape",
+ "espace",
+ "peaces"
+ ],
+ "cnoopsu": [
+ "coupons",
+ "soupcon"
+ ],
+ "aciils": [
+ "sialic",
+ "silica"
+ ],
+ "abost": [
+ "basto",
+ "boast",
+ "boats",
+ "botas",
+ "sabot"
+ ],
+ "acegln": [
+ "cangle",
+ "glance"
+ ],
+ "ellst": [
+ "stell",
+ "tells"
+ ],
+ "bdemoors": [
+ "bedrooms",
+ "boredoms"
+ ],
+ "aklst": [
+ "stalk",
+ "talks"
+ ],
+ "ailrst": [
+ "latris",
+ "strail",
+ "strial",
+ "trails",
+ "trials"
+ ],
+ "aemrsst": [
+ "masters",
+ "streams"
+ ],
+ "aabelrt": [
+ "alberta",
+ "latebra",
+ "ratable"
+ ],
+ "gor": [
+ "gor",
+ "gro",
+ "org",
+ "rog"
+ ],
+ "cmmnoos": [
+ "commons",
+ "consomm"
+ ],
+ "adfru": [
+ "faurd",
+ "fraud"
+ ],
+ "cemprstu": [
+ "crumpets",
+ "spectrum"
+ ],
+ "akoy": [
+ "kayo",
+ "oaky",
+ "okay"
+ ],
+ "aehimpss": [
+ "emphasis",
+ "misshape"
+ ],
+ "egorr": [
+ "gorer",
+ "roger"
+ ],
+ "acepst": [
+ "aspect",
+ "epacts"
+ ],
+ "aeemosw": [
+ "awesome",
+ "waesome"
+ ],
+ "cnostu": [
+ "counts",
+ "tucson",
+ "uncost"
+ ],
+ "cdeipr": [
+ "percid",
+ "priced"
+ ],
+ "achrs": [
+ "chars",
+ "crash"
+ ],
+ "filt": [
+ "filt",
+ "flit",
+ "lift"
+ ],
+ "ddeeirs": [
+ "derides",
+ "desired",
+ "resided"
+ ],
+ "einrt": [
+ "inert",
+ "inter",
+ "niter",
+ "nitre",
+ "retin",
+ "trine"
+ ],
+ "celors": [
+ "ceorls",
+ "closer",
+ "cresol",
+ "escrol"
+ ],
+ "ails": [
+ "ails",
+ "lasi",
+ "lias",
+ "lisa",
+ "sail",
+ "sial"
+ ],
+ "ceinprss": [
+ "crispens",
+ "princess"
+ ],
+ "aprsy": [
+ "prays",
+ "raspy",
+ "spary",
+ "spray"
+ ],
+ "deentx": [
+ "dentex",
+ "extend"
+ ],
+ "adors": [
+ "dorsa",
+ "roads",
+ "sarod",
+ "sorda"
+ ],
+ "apt": [
+ "apt",
+ "pat",
+ "pta",
+ "tap"
+ ],
+ "adry": [
+ "adry",
+ "dray",
+ "yard"
+ ],
+ "eeimmors": [
+ "memories",
+ "memorise"
+ ],
+ "aceerst": [
+ "cerates",
+ "creates",
+ "ecartes",
+ "secreta"
+ ],
+ "acefs": [
+ "cafes",
+ "faces"
+ ],
+ "amory": [
+ "mayor",
+ "moray"
+ ],
+ "aens": [
+ "anes",
+ "sane",
+ "sean",
+ "sena"
+ ],
+ "aenorst": [
+ "atoners",
+ "noreast",
+ "orantes",
+ "rosetan",
+ "seatron",
+ "senator",
+ "treason"
+ ],
+ "adegrs": [
+ "degras",
+ "egards",
+ "grades"
+ ],
+ "acnoorst": [
+ "cartoons",
+ "corantos",
+ "ostracon",
+ "socotran"
+ ],
+ "opru": [
+ "pour",
+ "roup"
+ ],
+ "egr": [
+ "erg",
+ "ger",
+ "gre",
+ "reg"
+ ],
+ "dggilno": [
+ "godling",
+ "golding",
+ "lodging"
+ ],
+ "dstu": [
+ "dust",
+ "stud"
+ ],
+ "eeilnrty": [
+ "entirely",
+ "lientery"
+ ],
+ "acdeelpr": [
+ "parceled",
+ "replaced"
+ ],
+ "elosss": [
+ "losses",
+ "sossle"
+ ],
+ "abcmot": [
+ "combat",
+ "tombac"
+ ],
+ "aekls": [
+ "alkes",
+ "kales",
+ "lakes",
+ "leaks",
+ "sakel",
+ "slake"
+ ],
+ "adinnoost": [
+ "anisodont",
+ "donations",
+ "sondation"
+ ],
+ "adiry": [
+ "dairy",
+ "diary",
+ "yaird"
+ ],
+ "gikns": [
+ "ginks",
+ "kings"
+ ],
+ "ghinoost": [
+ "shooting",
+ "soothing"
+ ],
+ "eknt": [
+ "kent",
+ "knet"
+ ],
+ "adds": [
+ "adds",
+ "dads"
+ ],
+ "aceeinorstvv": [
+ "conservative",
+ "conversative"
+ ],
+ "chkos": [
+ "hocks",
+ "shock"
+ ],
+ "aabdor": [
+ "aboard",
+ "aborad",
+ "abroad"
+ ],
+ "benoy": [
+ "boney",
+ "ebony"
+ ],
+ "ain": [
+ "ain",
+ "ani",
+ "ian"
+ ],
+ "aeehmoprst": [
+ "atmosphere",
+ "shapometer"
+ ],
+ "ikss": [
+ "kiss",
+ "skis"
+ ],
+ "abest": [
+ "abets",
+ "baste",
+ "bates",
+ "beast",
+ "beats",
+ "betas",
+ "estab",
+ "sebat",
+ "tabes"
+ ],
+ "aegrstt": [
+ "tagster",
+ "targets"
+ ],
+ "celnosu": [
+ "counsel",
+ "unclose"
+ ],
+ "adrsy": [
+ "drays",
+ "dryas",
+ "yards"
+ ],
+ "dgnoor": [
+ "drongo",
+ "gordon"
+ ],
+ "dmo": [
+ "dom",
+ "mod"
+ ],
+ "aefmrrs": [
+ "farmers",
+ "framers"
+ ],
+ "eeiqrsu": [
+ "esquire",
+ "queries",
+ "risquee"
+ ],
+ "hrsu": [
+ "rhus",
+ "rush"
+ ],
+ "celrstu": [
+ "cluster",
+ "custrel",
+ "cutlers",
+ "relucts"
+ ],
+ "eerssv": [
+ "serves",
+ "severs",
+ "sevres",
+ "verses"
+ ],
+ "eiprrssu": [
+ "spurries",
+ "surprise",
+ "uprisers"
+ ],
+ "aailprt": [
+ "partial",
+ "patrial"
+ ],
+ "celopsu": [
+ "closeup",
+ "couples",
+ "culpose",
+ "opuscle",
+ "ploceus",
+ "upclose"
+ ],
+ "agiknnr": [
+ "narking",
+ "ranking"
+ ],
+ "cst": [
+ "cst",
+ "cts",
+ "sct"
+ ],
+ "ceo": [
+ "coe",
+ "eco"
+ ],
+ "aacelp": [
+ "aplace",
+ "palace"
+ ],
+ "beglo": [
+ "bogle",
+ "globe"
+ ],
+ "ackr": [
+ "cark",
+ "rack"
+ ],
+ "acdeiimnot": [
+ "decimation",
+ "medication"
+ ],
+ "aeehorsuw": [
+ "housewear",
+ "warehouse"
+ ],
+ "ceeiprt": [
+ "ereptic",
+ "precite",
+ "receipt"
+ ],
+ "ghost": [
+ "ghost",
+ "goths"
+ ],
+ "boss": [
+ "boss",
+ "sobs"
+ ],
+ "deipr": [
+ "pride",
+ "pried",
+ "redip",
+ "riped"
+ ],
+ "ehikt": [
+ "keith",
+ "kithe"
+ ],
+ "adiln": [
+ "danli",
+ "ladin",
+ "linda",
+ "nidal"
+ ],
+ "cehil": [
+ "chiel",
+ "chile"
+ ],
+ "aann": [
+ "anan",
+ "anna",
+ "nana"
+ ],
+ "elnpty": [
+ "pentyl",
+ "plenty"
+ ],
+ "loos": [
+ "loos",
+ "oslo",
+ "sloo",
+ "solo",
+ "sool"
+ ],
+ "ahortt": [
+ "athort",
+ "throat"
+ ],
+ "eirstw": [
+ "twiers",
+ "wister",
+ "wriest",
+ "writes"
+ ],
+ "adps": [
+ "daps",
+ "pads",
+ "spad"
+ ],
+ "eqstu": [
+ "quest",
+ "squet"
+ ],
+ "ceeginnrs": [
+ "screening",
+ "secerning"
+ ],
+ "anrst": [
+ "rants",
+ "starn",
+ "tarns",
+ "trans"
+ ],
+ "eeinopr": [
+ "pereion",
+ "peronei",
+ "pioneer"
+ ],
+ "aabcort": [
+ "abactor",
+ "acrobat"
+ ],
+ "aelpst": [
+ "palest",
+ "palets",
+ "pastel",
+ "petals",
+ "plates",
+ "pleats",
+ "septal",
+ "staple",
+ "tepals"
+ ],
+ "acers": [
+ "acres",
+ "arces",
+ "cares",
+ "carse",
+ "caser",
+ "ceras",
+ "cesar",
+ "escar",
+ "races",
+ "sacre",
+ "scare",
+ "scrae",
+ "serac"
+ ],
+ "acehiltt": [
+ "athletic",
+ "thetical"
+ ],
+ "egillnt": [
+ "gillnet",
+ "telling"
+ ],
+ "aaclost": [
+ "catalos",
+ "coastal",
+ "salacot"
+ ],
+ "demos": [
+ "demos",
+ "domes",
+ "modes"
+ ],
+ "aekw": [
+ "wake",
+ "weak",
+ "weka"
+ ],
+ "aghnruy": [
+ "ahungry",
+ "hungary"
+ ],
+ "aeelrrtv": [
+ "retravel",
+ "revertal",
+ "traveler"
+ ],
+ "aln": [
+ "aln",
+ "lan"
+ ],
+ "eemny": [
+ "enemy",
+ "yemen"
+ ],
+ "giinrs": [
+ "rising",
+ "siring"
+ ],
+ "ellsw": [
+ "swell",
+ "wells"
+ ],
+ "ghiinst": [
+ "histing",
+ "insight"
+ ],
+ "cdeeirrstt": [
+ "derestrict",
+ "restricted"
+ ],
+ "ceersst": [
+ "cresset",
+ "resects",
+ "secrets"
+ ],
+ "aelrtt": [
+ "artlet",
+ "latter",
+ "rattel",
+ "rattle",
+ "talter",
+ "tartle",
+ "tatler"
+ ],
+ "acehmnrst": [
+ "merchants",
+ "starchmen"
+ ],
+ "aeilrrst": [
+ "retrials",
+ "trailers"
+ ],
+ "aeeprt": [
+ "petrea",
+ "repeat",
+ "retape"
+ ],
+ "aelnpty": [
+ "aplenty",
+ "penalty"
+ ],
+ "aeglsss": [
+ "gasless",
+ "glasses",
+ "sagless"
+ ],
+ "abeelns": [
+ "baleens",
+ "enables"
+ ],
+ "cen": [
+ "cen",
+ "enc"
+ ],
+ "bdeilru": [
+ "builder",
+ "rebuild"
+ ],
+ "errty": [
+ "retry",
+ "terry"
+ ],
+ "aegmnrstu": [
+ "argentums",
+ "arguments",
+ "mustanger"
+ ],
+ "aaenr": [
+ "anear",
+ "arean",
+ "arena"
+ ],
+ "ilppsu": [
+ "pupils",
+ "slipup",
+ "upslip"
+ ],
+ "aersttw": [
+ "stewart",
+ "swatter"
+ ],
+ "abst": [
+ "bast",
+ "bats",
+ "stab",
+ "tabs"
+ ],
+ "aaclsu": [
+ "ascula",
+ "calusa",
+ "casual",
+ "casula",
+ "causal"
+ ],
+ "hilops": [
+ "philos",
+ "polish"
+ ],
+ "ellovy": [
+ "lovely",
+ "volley"
+ ],
+ "aerstx": [
+ "extras",
+ "sextar",
+ "taxers"
+ ],
+ "acelsu": [
+ "caelus",
+ "casule",
+ "caules",
+ "clause"
+ ],
+ "einp": [
+ "pein",
+ "pien",
+ "pine"
+ ],
+ "cgilnoo": [
+ "cooling",
+ "locoing"
+ ],
+ "dent": [
+ "dent",
+ "detn",
+ "tend"
+ ],
+ "ckrstu": [
+ "struck",
+ "trucks"
+ ],
+ "cdeiorv": [
+ "cervoid",
+ "divorce"
+ ],
+ "aalru": [
+ "aural",
+ "laura"
+ ],
+ "ehopprs": [
+ "hoppers",
+ "shopper"
+ ],
+ "kooty": [
+ "kyoto",
+ "tokyo"
+ ],
+ "alprty": [
+ "paltry",
+ "partly",
+ "raptly"
+ ],
+ "acdny": [
+ "candy",
+ "dancy"
+ ],
+ "illps": [
+ "pills",
+ "spill"
+ ],
+ "egirt": [
+ "greit",
+ "tiger",
+ "tigre"
+ ],
+ "enorss": [
+ "senors",
+ "sensor",
+ "snores"
+ ],
+ "aeglns": [
+ "angels",
+ "angles",
+ "gansel",
+ "gleans"
+ ],
+ "acdiinorst": [
+ "ancistroid",
+ "indicators"
+ ],
+ "adeels": [
+ "leased",
+ "sealed"
+ ],
+ "ahit": [
+ "aith",
+ "hait",
+ "hati",
+ "thai"
+ ],
+ "defr": [
+ "derf",
+ "ferd",
+ "fred"
+ ],
+ "acilmnopt": [
+ "complaint",
+ "compliant"
+ ],
+ "ceenss": [
+ "censes",
+ "scenes"
+ ],
+ "almor": [
+ "molar",
+ "moral",
+ "romal"
+ ],
+ "adu": [
+ "aud",
+ "dau"
+ ],
+ "efginr": [
+ "finger",
+ "fringe"
+ ],
+ "eekps": [
+ "keeps",
+ "peeks",
+ "pekes"
+ ],
+ "acelot": [
+ "acetol",
+ "colate",
+ "locate"
+ ],
+ "adeinrt": [
+ "andrite",
+ "antired",
+ "detrain",
+ "diantre",
+ "radient",
+ "randite",
+ "trained"
+ ],
+ "eorss": [
+ "roses",
+ "sores"
+ ],
+ "abls": [
+ "albs",
+ "bals",
+ "blas",
+ "labs",
+ "slab"
+ ],
+ "abder": [
+ "ardeb",
+ "barde",
+ "bared",
+ "beard",
+ "bread",
+ "debar"
+ ],
+ "denoow": [
+ "enwood",
+ "wooden"
+ ],
+ "ghotu": [
+ "ought",
+ "tough"
+ ],
+ "eil": [
+ "eli",
+ "ile",
+ "lei",
+ "lie"
+ ],
+ "cehst": [
+ "chest",
+ "stech"
+ ],
+ "einnops": [
+ "pension",
+ "pinones"
+ ],
+ "eeenrsuv": [
+ "revenues",
+ "unreeves",
+ "unsevere"
+ ],
+ "acgir": [
+ "agric",
+ "cigar",
+ "craig"
+ ],
+ "eefhlrs": [
+ "flesher",
+ "herself"
+ ],
+ "ceiinoprs": [
+ "coinspire",
+ "precision"
+ ],
+ "eeerrssv": [
+ "reserves",
+ "reverses",
+ "severers"
+ ],
+ "elosv": [
+ "loves",
+ "solve",
+ "voles"
+ ],
+ "horsst": [
+ "horsts",
+ "shorts"
+ ],
+ "cdeinooprrtu": [
+ "proreduction",
+ "reproduction"
+ ],
+ "deegiinnrst": [
+ "ingredients",
+ "tenderising"
+ ],
+ "cdeeorrr": [
+ "recorder",
+ "rerecord"
+ ],
+ "acnny": [
+ "canny",
+ "nancy"
+ ],
+ "acly": [
+ "acyl",
+ "clay",
+ "lacy"
+ ],
+ "acehpst": [
+ "hepcats",
+ "patches"
+ ],
+ "defnru": [
+ "funder",
+ "refund"
+ ],
+ "nostw": [
+ "nowts",
+ "towns",
+ "wonts"
+ ],
+ "ceeinoprt": [
+ "prenotice",
+ "reception"
+ ],
+ "aeilms": [
+ "amiles",
+ "asmile",
+ "mailes",
+ "mesail",
+ "mesial",
+ "samiel"
+ ],
+ "cprsuy": [
+ "cyprus",
+ "sprucy"
+ ],
+ "ddos": [
+ "dods",
+ "odds"
+ ],
+ "deiinrs": [
+ "insider",
+ "siderin"
+ ],
+ "aeimnrss": [
+ "arsenism",
+ "seminars"
+ ],
+ "aekmrs": [
+ "makers",
+ "masker",
+ "remask"
+ ],
+ "aehrst": [
+ "earths",
+ "haster",
+ "haters",
+ "hearst",
+ "hearts"
+ ],
+ "eev": [
+ "eve",
+ "vee"
+ ],
+ "acerrt": [
+ "arrect",
+ "carter",
+ "crater",
+ "recart",
+ "tracer"
+ ],
+ "acmr": [
+ "cram",
+ "marc"
+ ],
+ "adeelps": [
+ "delapse",
+ "elapsed",
+ "pleased",
+ "sepaled"
+ ],
+ "deilwy": [
+ "dewily",
+ "widely",
+ "wieldy"
+ ],
+ "aehprs": [
+ "phaser",
+ "phrase",
+ "raphes",
+ "seraph",
+ "shaper",
+ "sherpa",
+ "shrape"
+ ],
+ "eeginnu": [
+ "genuine",
+ "ingenue"
+ ],
+ "agiinrs": [
+ "airings",
+ "arising",
+ "raising"
+ ],
+ "aadeiprs": [
+ "paradise",
+ "sparidae"
+ ],
+ "aders": [
+ "dares",
+ "dears",
+ "rased",
+ "reads"
+ ],
+ "elors": [
+ "lores",
+ "loser",
+ "orles",
+ "orsel",
+ "roles",
+ "rosel",
+ "soler",
+ "sorel"
+ ],
+ "aefl": [
+ "alef",
+ "feal",
+ "flea",
+ "leaf"
+ ],
+ "deeils": [
+ "delies",
+ "diesel",
+ "ediles",
+ "elides",
+ "sedile",
+ "seidel"
+ ],
+ "erssuv": [
+ "servus",
+ "versus"
+ ],
+ "dor": [
+ "dor",
+ "ord",
+ "rod"
+ ],
+ "adisu": [
+ "saudi",
+ "udasi"
+ ],
+ "hrs": [
+ "hrs",
+ "shr"
+ ],
+ "ikls": [
+ "ilks",
+ "lisk",
+ "silk",
+ "skil",
+ "slik"
+ ],
+ "aeknr": [
+ "anker",
+ "karen",
+ "kearn",
+ "naker",
+ "nerka"
+ ],
+ "cdeilmop": [
+ "compiled",
+ "complied"
+ ],
+ "acimnort": [
+ "macrotin",
+ "romantic"
+ ],
+ "adeeelrv": [
+ "laveered",
+ "revealed"
+ ],
+ "abelrt": [
+ "albert",
+ "balter",
+ "batler",
+ "labret",
+ "tabler",
+ "tarble"
+ ],
+ "bilorst": [
+ "bristol",
+ "strobil"
+ ],
+ "acl": [
+ "alc",
+ "cal",
+ "lac",
+ "lca"
+ ],
+ "inooprst": [
+ "notropis",
+ "portions",
+ "positron",
+ "sorption"
+ ],
+ "ceorsst": [
+ "corsets",
+ "costers",
+ "escorts",
+ "scoters",
+ "sectors"
+ ],
+ "aelmsu": [
+ "amelus",
+ "samuel",
+ "ulemas"
+ ],
+ "fist": [
+ "fist",
+ "fits",
+ "sift"
+ ],
+ "adegrrs": [
+ "graders",
+ "regards"
+ ],
+ "hrtu": [
+ "hurt",
+ "ruth",
+ "thru"
+ ],
+ "acehimnry": [
+ "hemicrany",
+ "machinery"
+ ],
+ "gim": [
+ "gim",
+ "mig"
+ ],
+ "aenrrw": [
+ "rewarn",
+ "warner",
+ "warren"
+ ],
+ "ilps": [
+ "lips",
+ "lisp",
+ "slip"
+ ],
+ "ddeistu": [
+ "studdie",
+ "studied"
+ ],
+ "aeimr": [
+ "aimer",
+ "amire",
+ "maire",
+ "marie",
+ "ramie"
+ ],
+ "cstu": [
+ "cust",
+ "cuts",
+ "scut"
+ ],
+ "aeflnru": [
+ "earnful",
+ "flaneur",
+ "frenula",
+ "funeral",
+ "furlane"
+ ],
+ "aeginrrs": [
+ "earrings",
+ "grainers"
+ ],
+ "acehprst": [
+ "chapters",
+ "patchers"
+ ],
+ "deinns": [
+ "dennis",
+ "sinned"
+ ],
+ "aagmn": [
+ "amang",
+ "ganam",
+ "magna",
+ "manga"
+ ],
+ "cdeinot": [
+ "condite",
+ "ctenoid",
+ "deontic",
+ "noticed"
+ ],
+ "aeilrrty": [
+ "literary",
+ "trailery"
+ ],
+ "agilnss": [
+ "glassin",
+ "signals"
+ ],
+ "acps": [
+ "caps",
+ "pacs",
+ "scap"
+ ],
+ "alt": [
+ "alt",
+ "lat",
+ "tal"
+ ],
+ "aaglno": [
+ "agonal",
+ "analog",
+ "angola"
+ ],
+ "aacfil": [
+ "cafila",
+ "facial"
+ ],
+ "aelntt": [
+ "latent",
+ "latten",
+ "nattle",
+ "talent",
+ "tantle"
+ ],
+ "eeekrs": [
+ "kreese",
+ "reseek",
+ "seeker",
+ "sekere"
+ ],
+ "hoost": [
+ "hoots",
+ "shoot",
+ "sooth",
+ "sotho",
+ "toosh"
+ ],
+ "effost": [
+ "offset",
+ "setoff"
+ ],
+ "eeilt": [
+ "elite",
+ "telei"
+ ],
+ "inps": [
+ "insp",
+ "nips",
+ "pins",
+ "snip",
+ "spin"
+ ],
+ "dehissw": [
+ "swedish",
+ "swished"
+ ],
+ "emops": [
+ "epsom",
+ "mopes",
+ "poems",
+ "pomes"
+ ],
+ "boort": [
+ "boort",
+ "robot"
+ ],
+ "einsstw": [
+ "wisents",
+ "witness"
+ ],
+ "aegsst": [
+ "sagest",
+ "stages"
+ ],
+ "deoprw": [
+ "powder",
+ "prowed"
+ ],
+ "aessss": [
+ "assess",
+ "sasses"
+ ],
+ "ahsw": [
+ "haws",
+ "shaw",
+ "shwa",
+ "wash"
+ ],
+ "enosst": [
+ "onsets",
+ "seston",
+ "setons",
+ "stenos",
+ "stones"
+ ],
+ "aceennrt": [
+ "centenar",
+ "entrance"
+ ],
+ "egmno": [
+ "emong",
+ "genom",
+ "gnome"
+ ],
+ "oorst": [
+ "roost",
+ "roots",
+ "rotos",
+ "toros",
+ "torso"
+ ],
+ "aacdeilnort": [
+ "declaration",
+ "redactional"
+ ],
+ "gilnos": [
+ "logins",
+ "losing",
+ "soling"
+ ],
+ "adeggst": [
+ "gadgets",
+ "stagged"
+ ],
+ "belno": [
+ "nobel",
+ "noble"
+ ],
+ "erv": [
+ "rev",
+ "ver"
+ ],
+ "eglops": [
+ "gospel",
+ "spogel"
+ ],
+ "eloos": [
+ "loose",
+ "oleos"
+ ],
+ "aims": [
+ "aims",
+ "amis",
+ "mias",
+ "saim",
+ "siam",
+ "sima"
+ ],
+ "ceeiinprt": [
+ "princeite",
+ "recipient"
+ ],
+ "giiklnn": [
+ "inkling",
+ "kilning",
+ "linking"
+ ],
+ "adeenr": [
+ "deaner",
+ "earned",
+ "endear",
+ "neared"
+ ],
+ "aciilms": [
+ "islamic",
+ "laicism",
+ "silicam"
+ ],
+ "acehilstt": [
+ "athletics",
+ "statelich"
+ ],
+ "aekprr": [
+ "parker",
+ "repark"
+ ],
+ "copr": [
+ "copr",
+ "corp",
+ "crop",
+ "porc",
+ "proc"
+ ],
+ "aops": [
+ "asop",
+ "paso",
+ "sapo",
+ "soap"
+ ],
+ "eilprt": [
+ "pretil",
+ "tripel",
+ "triple"
+ ],
+ "cdeersu": [
+ "descure",
+ "recused",
+ "reduces",
+ "rescued",
+ "secured",
+ "seducer"
+ ],
+ "einortu": [
+ "routine",
+ "tueiron"
+ ],
+ "aabcillsy": [
+ "asyllabic",
+ "basically"
+ ],
+ "ckors": [
+ "corks",
+ "rocks"
+ ],
+ "ainstt": [
+ "astint",
+ "taints",
+ "tanist",
+ "titans"
+ ],
+ "ghostu": [
+ "oughts",
+ "sought",
+ "toughs"
+ ],
+ "demnotu": [
+ "demount",
+ "mounted"
+ ],
+ "aabhitt": [
+ "habitat",
+ "tabitha"
+ ],
+ "adeimn": [
+ "aidmen",
+ "daimen",
+ "damine",
+ "demain",
+ "dimane",
+ "maiden",
+ "median",
+ "medina"
+ ],
+ "gnsu": [
+ "gnus",
+ "guns",
+ "snug",
+ "sung"
+ ],
+ "acennrs": [
+ "canners",
+ "scanner"
+ ],
+ "eehinr": [
+ "herein",
+ "inhere"
+ ],
+ "aadeimnt": [
+ "aminated",
+ "animated",
+ "diamante",
+ "mandaite",
+ "mantidae"
+ ],
+ "ior": [
+ "rio",
+ "roi"
+ ],
+ "ehor": [
+ "hero",
+ "hoer",
+ "hore",
+ "rheo"
+ ],
+ "eeginrt": [
+ "giterne",
+ "integer",
+ "retinge",
+ "treeing"
+ ],
+ "abcehlor": [
+ "bachelor",
+ "crabhole",
+ "lochaber"
+ ],
+ "afgilln": [
+ "falling",
+ "fingall"
+ ],
+ "aceprt": [
+ "carpet",
+ "peract",
+ "preact"
+ ],
+ "eelnss": [
+ "lenses",
+ "lessen"
+ ],
+ "abinry": [
+ "binary",
+ "brainy"
+ ],
+ "addeentt": [
+ "attended",
+ "dentated"
+ ],
+ "aciilnoot": [
+ "coalition",
+ "coitional",
+ "lociation"
+ ],
+ "aelrtw": [
+ "lawter",
+ "walter"
+ ],
+ "aegw": [
+ "waeg",
+ "wage",
+ "wega"
+ ],
+ "aalst": [
+ "atlas",
+ "salat",
+ "salta",
+ "talas"
+ ],
+ "adnw": [
+ "dawn",
+ "wand"
+ ],
+ "eefls": [
+ "feels",
+ "flees"
+ ],
+ "eorrttu": [
+ "torture",
+ "trouter",
+ "tutorer"
+ ],
+ "aclr": [
+ "carl",
+ "clar"
+ ],
+ "acot": [
+ "coat",
+ "taco"
+ ],
+ "mrs": [
+ "mrs",
+ "rms"
+ ],
+ "aceinnort": [
+ "connarite",
+ "container",
+ "cotarnine",
+ "crenation",
+ "narcotine"
+ ],
+ "app": [
+ "app",
+ "pap",
+ "ppa"
+ ],
+ "eioprrssuv": [
+ "proviruses",
+ "supervisor"
+ ],
+ "coprs": [
+ "corps",
+ "crops"
+ ],
+ "acorst": [
+ "actors",
+ "arctos",
+ "castor",
+ "castro",
+ "costar",
+ "ostrca",
+ "scrota",
+ "tarocs"
+ ],
+ "eilrv": [
+ "ervil",
+ "levir",
+ "liver",
+ "livre",
+ "rivel",
+ "viler"
+ ],
+ "abeill": [
+ "alible",
+ "belial",
+ "labile",
+ "liable"
+ ],
+ "acellr": [
+ "caller",
+ "cellar",
+ "recall"
+ ],
+ "ademssu": [
+ "assumed",
+ "medusas"
+ ],
+ "adeeprrtu": [
+ "apertured",
+ "departure"
+ ],
+ "beefil": [
+ "befile",
+ "belief"
+ ],
+ "dehlorsu": [
+ "shoulder",
+ "shoulerd"
+ ],
+ "cdeor": [
+ "coder",
+ "cored",
+ "credo",
+ "decor"
+ ],
+ "kloopu": [
+ "lookup",
+ "uplook"
+ ],
+ "ory": [
+ "yor",
+ "ory",
+ "roy"
+ ],
+ "ino": [
+ "ino",
+ "ion",
+ "oni"
+ ],
+ "adeeimrt": [
+ "diameter",
+ "diatreme"
+ ],
+ "eefinr": [
+ "enfire",
+ "ferine",
+ "fineer",
+ "infree",
+ "refine"
+ ],
+ "bddeir": [
+ "bedrid",
+ "bidder",
+ "birded"
+ ],
+ "eginrs": [
+ "reigns",
+ "renigs",
+ "resign",
+ "resing",
+ "sering",
+ "signer",
+ "singer"
+ ],
+ "aensv": [
+ "avens",
+ "evans",
+ "naves",
+ "vanes"
+ ],
+ "adehlr": [
+ "hareld",
+ "harled",
+ "herald"
+ ],
+ "afils": [
+ "alifs",
+ "fails"
+ ],
+ "eikn": [
+ "enki",
+ "kine",
+ "nike"
+ ],
+ "eeiinnnorttv": [
+ "intervention",
+ "introvenient"
+ ],
+ "gilnpu": [
+ "gulpin",
+ "puling"
+ ],
+ "aacinorttt": [
+ "attraction",
+ "tractation"
+ ],
+ "acdfiiimnoot": [
+ "domification",
+ "modification"
+ ],
+ "aceil": [
+ "alice",
+ "celia",
+ "elaic",
+ "ileac"
+ ],
+ "aailnst": [
+ "lanista",
+ "saliant",
+ "santali"
+ ],
+ "deer": [
+ "deer",
+ "dere",
+ "dree",
+ "rede",
+ "reed"
+ ],
+ "ceim": [
+ "emic",
+ "mice"
+ ],
+ "adilpry": [
+ "pyralid",
+ "rapidly"
+ ],
+ "empt": [
+ "empt",
+ "temp"
+ ],
+ "inort": [
+ "intro",
+ "iortn",
+ "nitro",
+ "norit"
+ ],
+ "aacenrssu": [
+ "anacruses",
+ "assurance"
+ ],
+ "astv": [
+ "tavs",
+ "vast",
+ "vats"
+ ],
+ "eilnotu": [
+ "elution",
+ "outline"
+ ],
+ "aejns": [
+ "janes",
+ "jeans"
+ ],
+ "acefiiinortv": [
+ "revification",
+ "verification"
+ ],
+ "ddo": [
+ "dod",
+ "odd"
+ ],
+ "aprw": [
+ "warp",
+ "wrap"
+ ],
+ "eefrrs": [
+ "freers",
+ "freres",
+ "refers"
+ ],
+ "dmoo": [
+ "doom",
+ "modo",
+ "mood"
+ ],
+ "agims": [
+ "agism",
+ "sigma"
+ ],
+ "addemns": [
+ "demands",
+ "maddens"
+ ],
+ "eegilnps": [
+ "peelings",
+ "sleeping",
+ "speeling"
+ ],
+ "etx": [
+ "ext",
+ "tex"
+ ],
+ "abem": [
+ "ambe",
+ "beam",
+ "bema"
+ ],
+ "adegginnr": [
+ "dangering",
+ "deranging",
+ "gandering",
+ "gardening"
+ ],
+ "aeirrv": [
+ "arrive",
+ "varier"
+ ],
+ "acehorrst": [
+ "carthorse",
+ "horsecart",
+ "orchestra"
+ ],
+ "ensstu": [
+ "sunset",
+ "unsets"
+ ],
+ "eemoorrv": [
+ "moreover",
+ "overmore"
+ ],
+ "adefmr": [
+ "farmed",
+ "framed"
+ ],
+ "aacillnoot": [
+ "allocation",
+ "locational"
+ ],
+ "aessy": [
+ "eyass",
+ "essay"
+ ],
+ "acmps": [
+ "camps",
+ "scamp"
+ ],
+ "acert": [
+ "caret",
+ "carte",
+ "cater",
+ "cerat",
+ "crate",
+ "creat",
+ "creta",
+ "ecart",
+ "react",
+ "recta",
+ "trace"
+ ],
+ "ackps": [
+ "packs",
+ "spack"
+ ],
+ "aaimnor": [
+ "amanori",
+ "moarian"
+ ],
+ "hotu": [
+ "hout",
+ "thou"
+ ],
+ "aeglrty": [
+ "greatly",
+ "regalty"
+ ],
+ "akms": [
+ "kasm",
+ "mask"
+ ],
+ "aeghhoopprrt": [
+ "photographer",
+ "rephotograph"
+ ],
+ "fimnor": [
+ "formin",
+ "inform"
+ ],
+ "aclo": [
+ "alco",
+ "coal",
+ "cola",
+ "loca"
+ ],
+ "aeggimnss": [
+ "gigmaness",
+ "messaging"
+ ],
+ "einntt": [
+ "intent",
+ "nitent",
+ "tinnet"
+ ],
+ "aaelnpst": [
+ "platanes",
+ "pleasant"
+ ],
+ "ekops": [
+ "pokes",
+ "spoke"
+ ],
+ "agilmnps": [
+ "psalming",
+ "sampling"
+ ],
+ "deirw": [
+ "weird",
+ "wider",
+ "wierd",
+ "wired",
+ "wride",
+ "wried"
+ ],
+ "ilno": [
+ "lino",
+ "lion",
+ "loin",
+ "noil"
+ ],
+ "ehlos": [
+ "holes",
+ "hosel",
+ "sheol",
+ "shole"
+ ],
+ "abdel": [
+ "baled",
+ "blade"
+ ],
+ "aelms": [
+ "almes",
+ "amsel",
+ "lames",
+ "males",
+ "meals",
+ "melas",
+ "mesal",
+ "salem",
+ "samel"
+ ],
+ "acnnoy": [
+ "ancony",
+ "canyon"
+ ],
+ "goot": [
+ "goto",
+ "togo"
+ ],
+ "eemrst": [
+ "merest",
+ "mester",
+ "meters",
+ "metres",
+ "restem",
+ "retems",
+ "temser",
+ "termes"
+ ],
+ "eelmry": [
+ "yelmer",
+ "merely"
+ ],
+ "eimprst": [
+ "imprest",
+ "permits"
+ ],
+ "ilmmsu": [
+ "mulism",
+ "muslim"
+ ],
+ "eeelsv": [
+ "levees",
+ "sleeve"
+ ],
+ "aceelnr": [
+ "cleaner",
+ "enclear",
+ "reclean",
+ "relance"
+ ],
+ "beef": [
+ "beef",
+ "feeb"
+ ],
+ "deefgin": [
+ "feeding",
+ "feigned"
+ ],
+ "ekorst": [
+ "stoker",
+ "stroke",
+ "trokes"
+ ],
+ "aegimnrsu": [
+ "geraniums",
+ "measuring"
+ ],
+ "acd": [
+ "adc",
+ "cad",
+ "dca"
+ ],
+ "ahst": [
+ "hast",
+ "hats",
+ "shat",
+ "tash"
+ ],
+ "binor": [
+ "biron",
+ "inorb",
+ "robin"
+ ],
+ "ahnors": [
+ "rhason",
+ "sharon",
+ "shoran"
+ ],
+ "cpt": [
+ "cpt",
+ "pct"
+ ],
+ "frsu": [
+ "furs",
+ "surf"
+ ],
+ "adeeimnr": [
+ "adermine",
+ "remained"
+ ],
+ "dir": [
+ "dir",
+ "rid"
+ ],
+ "aeiilrs": [
+ "alisier",
+ "israeli",
+ "resilia"
+ ],
+ "cdor": [
+ "cord",
+ "dcor"
+ ],
+ "alv": [
+ "lav",
+ "val"
+ ],
+ "efhls": [
+ "flesh",
+ "shelf"
+ ],
+ "giimnt": [
+ "miting",
+ "timing"
+ ],
+ "aeginnprt": [
+ "enrapting",
+ "parenting"
+ ],
+ "foost": [
+ "foots",
+ "sfoot",
+ "stoof"
+ ],
+ "adehst": [
+ "deaths",
+ "hasted",
+ "sdeath"
+ ],
+ "aaiknrt": [
+ "katrina",
+ "tarkani"
+ ],
+ "ailms": [
+ "islam",
+ "ismal",
+ "limas",
+ "mails",
+ "salmi",
+ "simal"
+ ],
+ "denos": [
+ "doesn",
+ "nodes",
+ "nosed",
+ "sonde"
+ ],
+ "deess": [
+ "deess",
+ "essed",
+ "seeds"
+ ],
+ "cdeit": [
+ "cetid",
+ "cited",
+ "edict"
+ ],
+ "eilt": [
+ "itel",
+ "lite",
+ "teil",
+ "teli",
+ "tile"
+ ],
+ "defnoru": [
+ "founder",
+ "refound",
+ "underfo"
+ ],
+ "adeersv": [
+ "adverse",
+ "evaders"
+ ],
+ "egn": [
+ "eng",
+ "gen",
+ "neg"
+ ],
+ "acdeghirs": [
+ "discharge",
+ "scraighed"
+ ],
+ "nost": [
+ "nots",
+ "snot",
+ "tons"
+ ],
+ "aclor": [
+ "alcor",
+ "calor",
+ "carlo",
+ "carol",
+ "claro",
+ "coral"
+ ],
+ "ehnost": [
+ "ethnos",
+ "honest"
+ ],
+ "ackst": [
+ "stack",
+ "tacks"
+ ],
+ "aeehorssuw": [
+ "housewares",
+ "warehouses"
+ ],
+ "aeinrsstt": [
+ "resistant",
+ "straitens"
+ ],
+ "aghn": [
+ "ghan",
+ "hang"
+ ],
+ "aceorrt": [
+ "acroter",
+ "creator",
+ "reactor"
+ ],
+ "abemr": [
+ "amber",
+ "bearm",
+ "bemar",
+ "brame",
+ "bream",
+ "embar"
+ ],
+ "acekrrt": [
+ "retrack",
+ "tracker"
+ ],
+ "eeiprr": [
+ "perrie",
+ "pierre"
+ ],
+ "adeehst": [
+ "headset",
+ "sethead"
+ ],
+ "acelm": [
+ "camel",
+ "clame",
+ "cleam",
+ "macle"
+ ],
+ "almps": [
+ "lamps",
+ "palms",
+ "plasm",
+ "psalm",
+ "slamp"
+ ],
+ "degilnnruy": [
+ "enduringly",
+ "underlying"
+ ],
+ "chi": [
+ "chi",
+ "hic",
+ "ich"
+ ],
+ "acehs": [
+ "aches",
+ "chase"
+ ],
+ "ceorstuy": [
+ "cosurety",
+ "courtesy"
+ ],
+ "aehnst": [
+ "athens",
+ "hasten",
+ "snathe",
+ "sneath",
+ "thanes"
+ ],
+ "eos": [
+ "eos",
+ "oes",
+ "ose",
+ "soe"
+ ],
+ "deeirrt": [
+ "retired",
+ "retried",
+ "tireder"
+ ],
+ "ips": [
+ "ips",
+ "pis",
+ "psi",
+ "sip"
+ ],
+ "aekmrrs": [
+ "markers",
+ "remarks"
+ ],
+ "aceesstt": [
+ "casettes",
+ "cassette"
+ ],
+ "acginsu": [
+ "causing",
+ "saucing"
+ ],
+ "aeilmnr": [
+ "lairmen",
+ "manlier",
+ "marline",
+ "mineral",
+ "railmen",
+ "ramline"
+ ],
+ "bruy": [
+ "bury",
+ "ruby"
+ ],
+ "eeginrst": [
+ "energist",
+ "gentries",
+ "ingester",
+ "integers",
+ "reesting",
+ "steering"
+ ],
+ "ahiprs": [
+ "parish",
+ "raphis",
+ "rhapis"
+ ],
+ "ceenrss": [
+ "censers",
+ "screens",
+ "secerns"
+ ],
+ "flosw": [
+ "flows",
+ "fowls",
+ "wolfs"
+ ],
+ "adimstu": [
+ "dumaist",
+ "stadium"
+ ],
+ "imns": [
+ "mins",
+ "nims"
+ ],
+ "cnoopu": [
+ "coupon",
+ "uncoop"
+ ],
+ "emst": [
+ "mest",
+ "mets",
+ "stem"
+ ],
+ "addersw": [
+ "edwards",
+ "swadder",
+ "swarded",
+ "wadders"
+ ],
+ "aaelnrstt": [
+ "alterants",
+ "tarletans",
+ "translate"
+ ],
+ "adeggt": [
+ "gadget",
+ "tagged"
+ ],
+ "deotv": [
+ "devot",
+ "voted"
+ ],
+ "eikllr": [
+ "killer",
+ "rekill"
+ ],
+ "beiks": [
+ "bikes",
+ "kibes"
+ ],
+ "entu": [
+ "neut",
+ "tune"
+ ],
+ "adehps": [
+ "hasped",
+ "pashed",
+ "phased",
+ "shaped"
+ ],
+ "aefmrr": [
+ "farmer",
+ "framer"
+ ],
+ "cenorstu": [
+ "construe",
+ "counters",
+ "recounts",
+ "trounces"
+ ],
+ "pstu": [
+ "puts",
+ "sput",
+ "supt",
+ "tups"
+ ],
+ "ceeflprty": [
+ "perfectly",
+ "prefectly"
+ ],
+ "aelsv": [
+ "laves",
+ "salve",
+ "selva",
+ "slave",
+ "vales",
+ "valse",
+ "veals"
+ ],
+ "emo": [
+ "eom",
+ "meo",
+ "moe"
+ ],
+ "eehors": [
+ "heroes",
+ "reshoe"
+ ],
+ "adeinpt": [
+ "depaint",
+ "inadept",
+ "painted",
+ "patined"
+ ],
+ "ahilnoortz": [
+ "horizontal",
+ "notorhizal"
+ ],
+ "deelrstu": [
+ "deluster",
+ "lustered",
+ "resulted",
+ "ulstered"
+ ],
+ "acehilt": [
+ "alethic",
+ "ethical",
+ "thecial"
+ ],
+ "aceirrrs": [
+ "carriers",
+ "scarrier"
+ ],
+ "bdeilrsu": [
+ "builders",
+ "rebuilds"
+ ],
+ "egglrstu": [
+ "gurglets",
+ "struggle"
+ ],
+ "aelnrtu": [
+ "laurent",
+ "naturel",
+ "neutral",
+ "unalert"
+ ],
+ "efhirs": [
+ "fisher",
+ "sherif"
+ ],
+ "aeprss": [
+ "aspers",
+ "parses",
+ "passer",
+ "prases",
+ "repass",
+ "spares",
+ "sparse",
+ "spears"
+ ],
+ "abeginr": [
+ "bearing",
+ "begrain",
+ "brainge",
+ "gribane",
+ "rigbane"
+ ],
+ "abdr": [
+ "bard",
+ "brad",
+ "darb",
+ "drab"
+ ],
+ "bcmoo": [
+ "combo",
+ "coomb"
+ ],
+ "einorss": [
+ "seniors",
+ "sonsier"
+ ],
+ "aaciinottv": [
+ "activation",
+ "cavitation"
+ ],
+ "dos": [
+ "dos",
+ "ods",
+ "sod"
+ ],
+ "ailt": [
+ "alit",
+ "atli",
+ "ital",
+ "lait",
+ "lati",
+ "tail",
+ "tali"
+ ],
+ "eilnotv": [
+ "violent",
+ "volenti"
+ ],
+ "abins": [
+ "basin",
+ "nabis",
+ "sabin"
+ ],
+ "opsu": [
+ "opus",
+ "soup"
+ ],
+ "cginorss": [
+ "crossing",
+ "scorings"
+ ],
+ "ceimrs": [
+ "crimes",
+ "scrime"
+ ],
+ "enort": [
+ "noter",
+ "notre",
+ "tenor",
+ "toner",
+ "trone"
+ ],
+ "aeltx": [
+ "exalt",
+ "latex"
+ ],
+ "abcehnrs": [
+ "branches",
+ "brechans"
+ ],
+ "aemnory": [
+ "anymore",
+ "romneya"
+ ],
+ "dehil": [
+ "delhi",
+ "heild",
+ "hidel",
+ "hield"
+ ],
+ "aeiln": [
+ "alien",
+ "aline",
+ "anile",
+ "elain",
+ "elian",
+ "laine",
+ "liane",
+ "linea"
+ ],
+ "acloort": [
+ "carotol",
+ "crotalo",
+ "locator"
+ ],
+ "ajnu": [
+ "jaun",
+ "juan"
+ ],
+ "aeilmss": [
+ "aimless",
+ "melissa",
+ "samiels",
+ "seismal"
+ ],
+ "ehisst": [
+ "heists",
+ "shiest",
+ "sithes",
+ "thesis"
+ ],
+ "lnnoy": [
+ "nylon",
+ "nonyl",
+ "nonly"
+ ],
+ "ckory": [
+ "corky",
+ "rocky"
+ ],
+ "aabginrs": [
+ "abrasing",
+ "bargains"
+ ],
+ "aegiinr": [
+ "igraine",
+ "nigeria"
+ ],
+ "enps": [
+ "pens",
+ "sepn"
+ ],
+ "deilnttu": [
+ "dilutent",
+ "untilted",
+ "untitled"
+ ],
+ "ggiinns": [
+ "signing",
+ "singing"
+ ],
+ "ailmopt": [
+ "optimal",
+ "palmito"
+ ],
+ "cloooprst": [
+ "procotols",
+ "protocols"
+ ],
+ "glnu": [
+ "gunl",
+ "lung"
+ ],
+ "censt": [
+ "cents",
+ "csnet",
+ "scent"
+ ],
+ "eerrstu": [
+ "retruse",
+ "ureters"
+ ],
+ "aadeeltuv": [
+ "devaluate",
+ "evaluated"
+ ],
+ "adesty": [
+ "stayed",
+ "steady"
+ ],
+ "eess": [
+ "eses",
+ "esse",
+ "sees"
+ ],
+ "aersv": [
+ "avers",
+ "raves",
+ "saver",
+ "versa"
+ ],
+ "deeemr": [
+ "deemer",
+ "meered",
+ "redeem",
+ "remede"
+ ],
+ "egorrs": [
+ "groser",
+ "rogers"
+ ],
+ "aginr": [
+ "agrin",
+ "argin",
+ "garni",
+ "grain"
+ ],
+ "eegimr": [
+ "emigre",
+ "regime"
+ ],
+ "ehissw": [
+ "wishes",
+ "wisshe"
+ ],
+ "ddeenp": [
+ "depend",
+ "pended"
+ ],
+ "deffir": [
+ "differ",
+ "riffed"
+ ],
+ "achimnost": [
+ "macintosh",
+ "monachist"
+ ],
+ "acimno": [
+ "anomic",
+ "camino",
+ "camion",
+ "conima",
+ "manioc",
+ "monica"
+ ],
+ "aeiprrs": [
+ "aspirer",
+ "parries",
+ "praiser",
+ "rapiers",
+ "raspier",
+ "repairs",
+ "serpari"
+ ],
+ "abehrt": [
+ "bather",
+ "bertha",
+ "breath"
+ ],
+ "celo": [
+ "cole",
+ "ecol"
+ ],
+ "amrt": [
+ "mart",
+ "tram"
+ ],
+ "acdeln": [
+ "calden",
+ "candle",
+ "lanced"
+ ],
+ "cdeloor": [
+ "colored",
+ "croodle",
+ "decolor"
+ ],
+ "eekss": [
+ "kesse",
+ "seeks",
+ "skees"
+ ],
+ "gilnov": [
+ "loving",
+ "voling"
+ ],
+ "ginortu": [
+ "outgrin",
+ "outring",
+ "routing",
+ "touring"
+ ],
+ "cdos": [
+ "cods",
+ "docs"
+ ],
+ "aeeilrrt": [
+ "irrelate",
+ "retailer"
+ ],
+ "aiimnstv": [
+ "nativism",
+ "vitamins"
+ ],
+ "aeeglnt": [
+ "angelet",
+ "elegant"
+ ],
+ "agins": [
+ "gains",
+ "signa"
+ ],
+ "eirssst": [
+ "resists",
+ "sisters"
+ ],
+ "aghilmorst": [
+ "algorithms",
+ "logarithms"
+ ],
+ "addimr": [
+ "madrid",
+ "riddam"
+ ],
+ "agimnr": [
+ "arming",
+ "ingram",
+ "margin"
+ ],
+ "aefk": [
+ "fake",
+ "feak"
+ ],
+ "adf": [
+ "afd",
+ "fad"
+ ],
+ "eorstv": [
+ "stover",
+ "strove",
+ "troves",
+ "voters"
+ ],
+ "ceru": [
+ "cure",
+ "ecru",
+ "eruc"
+ ],
+ "acdemmnor": [
+ "commander",
+ "recommand"
+ ],
+ "deilors": [
+ "soldier",
+ "solider"
+ ],
+ "ains": [
+ "ains",
+ "anis",
+ "ansi",
+ "nais",
+ "nasi",
+ "nias",
+ "sain",
+ "sina"
+ ],
+ "ijnstu": [
+ "injust",
+ "justin"
+ ],
+ "ghilopstt": [
+ "spotlight",
+ "stoplight"
+ ],
+ "cikrst": [
+ "strick",
+ "tricks"
+ ],
+ "bhrsu": [
+ "brush",
+ "buhrs",
+ "shrub"
+ ],
+ "aelnps": [
+ "naples",
+ "panels",
+ "planes"
+ ],
+ "aeprs": [
+ "apers",
+ "apres",
+ "asper",
+ "pares",
+ "parse",
+ "pears",
+ "prase",
+ "presa",
+ "rapes",
+ "reaps",
+ "repas",
+ "spaer",
+ "spare",
+ "spear"
+ ],
+ "cgiilosst": [
+ "glossitic",
+ "logistics"
+ ],
+ "bgilnow": [
+ "blowing",
+ "bowling"
+ ],
+ "irt": [
+ "rit",
+ "rti",
+ "tri"
+ ],
+ "adhins": [
+ "danish",
+ "sandhi"
+ ],
+ "alp": [
+ "alp",
+ "apl",
+ "lap",
+ "pal"
+ ],
+ "ikrst": [
+ "skirt",
+ "stirk"
+ ],
+ "adginrsw": [
+ "drawings",
+ "swarding"
+ ],
+ "elorsv": [
+ "lovers",
+ "solver"
+ ],
+ "acimot": [
+ "atomic",
+ "matico"
+ ],
+ "aabcir": [
+ "arabic",
+ "cairba"
+ ],
+ "amt": [
+ "amt",
+ "atm",
+ "mat",
+ "tam"
+ ],
+ "acehlr": [
+ "rachel",
+ "rechal"
+ ],
+ "enov": [
+ "nevo",
+ "oven"
+ ],
+ "achins": [
+ "chains",
+ "chinas"
+ ],
+ "cghiinstw": [
+ "switching",
+ "witchings"
+ ],
+ "aadeprst": [
+ "adapters",
+ "readapts"
+ ],
+ "imoprst": [
+ "imports",
+ "primost",
+ "tropism"
+ ],
+ "benorz": [
+ "bonzer",
+ "bronze"
+ ],
+ "adnsy": [
+ "dansy",
+ "sandy"
+ ],
+ "aaeinoprst": [
+ "anisoptera",
+ "asperation",
+ "separation"
+ ],
+ "cepsstu": [
+ "suscept",
+ "suspect"
+ ],
+ "acmor": [
+ "carom",
+ "coram",
+ "macro",
+ "marco"
+ ],
+ "deenrs": [
+ "denser",
+ "enders",
+ "resend",
+ "sender"
+ ],
+ "aadmnorty": [
+ "damnatory",
+ "mandatory"
+ ],
+ "gmy": [
+ "gym",
+ "myg"
+ ],
+ "iinottu": [
+ "intuito",
+ "tuition"
+ ],
+ "eopssu": [
+ "esopus",
+ "opuses",
+ "pousse",
+ "spouse"
+ ],
+ "ceiotx": [
+ "coxite",
+ "exotic"
+ ],
+ "ginpsu": [
+ "pignus",
+ "spuing"
+ ],
+ "aehrstt": [
+ "hatters",
+ "rathest",
+ "shatter",
+ "threats"
+ ],
+ "acms": [
+ "cams",
+ "macs",
+ "masc",
+ "scam"
+ ],
+ "ejlo": [
+ "joel",
+ "jole"
+ ],
+ "deorsty": [
+ "destroy",
+ "stroyed"
+ ],
+ "aostu": [
+ "aotus",
+ "autos",
+ "outas"
+ ],
+ "eeimprss": [
+ "emprises",
+ "impreses",
+ "mesprise",
+ "premises",
+ "spiremes"
+ ],
+ "eprry": [
+ "perry",
+ "pryer",
+ "repry"
+ ],
+ "denoz": [
+ "dozen",
+ "zendo",
+ "zoned"
+ ],
+ "eehtt": [
+ "teeth",
+ "theet",
+ "thete"
+ ],
+ "ampst": [
+ "stamp",
+ "tamps"
+ ],
+ "lostu": [
+ "lotus",
+ "louts",
+ "tolus"
+ ],
+ "aadeeprst": [
+ "asperated",
+ "estrapade",
+ "paederast",
+ "separated"
+ ],
+ "ant": [
+ "ant",
+ "nat",
+ "tan"
+ ],
+ "cdeeiirtv": [
+ "creditive",
+ "directive"
+ ],
+ "aerrstt": [
+ "ratters",
+ "restart",
+ "starter"
+ ],
+ "bdenru": [
+ "bunder",
+ "burden",
+ "burned",
+ "unbred"
+ ],
+ "aepst": [
+ "paste",
+ "pates",
+ "peats",
+ "septa",
+ "spate",
+ "tapes",
+ "tepas"
+ ],
+ "ilms": [
+ "mils",
+ "slim"
+ ],
+ "aelmp": [
+ "ample",
+ "elamp",
+ "maple"
+ ],
+ "eklu": [
+ "leuk",
+ "luke"
+ ],
+ "aeeilrrst": [
+ "retailers",
+ "serratile"
+ ],
+ "deopt": [
+ "depot",
+ "opted",
+ "toped"
+ ],
+ "eip": [
+ "epi",
+ "pie"
+ ],
+ "eiimnoss": [
+ "emission",
+ "misiones",
+ "simonies"
+ ],
+ "eept": [
+ "pete",
+ "tepe"
+ ],
+ "ceps": [
+ "ceps",
+ "psec",
+ "spec"
+ ],
+ "efinst": [
+ "feints",
+ "festin",
+ "finest",
+ "infest"
+ ],
+ "aelrty": [
+ "elytra",
+ "lyrate",
+ "raylet",
+ "realty",
+ "telary"
+ ],
+ "bow": [
+ "bow",
+ "wob"
+ ],
+ "aaenpprt": [
+ "apparent",
+ "trappean"
+ ],
+ "aciilnnorsttu": [
+ "instructional",
+ "nonaltruistic"
+ ],
+ "beopr": [
+ "probe",
+ "rebop"
+ ],
+ "diim": [
+ "imid",
+ "midi"
+ ],
+ "eiimnoprsss": [
+ "impressions",
+ "permissions"
+ ],
+ "eilott": [
+ "lottie",
+ "toilet",
+ "tolite"
+ ],
+ "adeknr": [
+ "danker",
+ "darken",
+ "endark",
+ "kanred",
+ "narked",
+ "ranked"
+ ],
+ "eorstu": [
+ "ouster",
+ "outers",
+ "routes",
+ "souter",
+ "stoure",
+ "touser",
+ "trouse"
+ ],
+ "ceeorrv": [
+ "coverer",
+ "recover"
+ ],
+ "aceehinrt": [
+ "catherine",
+ "heritance"
+ ],
+ "bdegu": [
+ "budge",
+ "debug"
+ ],
+ "cddeeoprru": [
+ "procedured",
+ "reproduced"
+ ],
+ "hno": [
+ "hon",
+ "noh"
+ ],
+ "aillsv": [
+ "vallis",
+ "villas"
+ ],
+ "eeginp": [
+ "epigne",
+ "genepi",
+ "peeing"
+ ],
+ "bgino": [
+ "bingo",
+ "boing"
+ ],
+ "notu": [
+ "tuno",
+ "unto"
+ ],
+ "acceimr": [
+ "ceramic",
+ "racemic"
+ ],
+ "apsy": [
+ "aspy",
+ "yaps",
+ "pays",
+ "pyas",
+ "spay"
+ ],
+ "efginrs": [
+ "fingers",
+ "fringes"
+ ],
+ "deeilrsv": [
+ "delivers",
+ "desilver",
+ "silvered",
+ "slivered"
+ ],
+ "deels": [
+ "deles",
+ "leeds",
+ "lesed"
+ ],
+ "acder": [
+ "acred",
+ "arced",
+ "cader",
+ "cadre",
+ "cared",
+ "cedar",
+ "cread",
+ "creda",
+ "raced"
+ ],
+ "aeehrstt": [
+ "earthset",
+ "streahte",
+ "theaters",
+ "theatres"
+ ],
+ "dflo": [
+ "dolf",
+ "fold"
+ ],
+ "abilr": [
+ "blair",
+ "brail",
+ "libra"
+ ],
+ "ehops": [
+ "hopes",
+ "phose",
+ "shope"
+ ],
+ "amnos": [
+ "manos",
+ "manso",
+ "mason",
+ "moans",
+ "monas",
+ "mosan",
+ "nomas"
+ ],
+ "civ": [
+ "civ",
+ "vic"
+ ],
+ "aimor": [
+ "maori",
+ "mario",
+ "moira"
+ ],
+ "ops": [
+ "ops",
+ "pos",
+ "sop"
+ ],
+ "aachtt": [
+ "attach",
+ "chatta"
+ ],
+ "ceeilnss": [
+ "licenses",
+ "silences"
+ ],
+ "ilstu": [
+ "litus",
+ "sluit",
+ "tulsi"
+ ],
+ "deiprs": [
+ "prides",
+ "prised",
+ "redips",
+ "spider",
+ "spired",
+ "spried"
+ ],
+ "hpsy": [
+ "hyps",
+ "phys",
+ "syph"
+ ],
+ "aegnrs": [
+ "angers",
+ "ganser",
+ "granes",
+ "ranges",
+ "sanger",
+ "serang"
+ ],
+ "dhnosu": [
+ "hounds",
+ "hudson",
+ "unshod"
+ ],
+ "adeilost": [
+ "diastole",
+ "isolated",
+ "sodalite",
+ "solidate"
+ ],
+ "eiimnrt": [
+ "interim",
+ "mintier",
+ "termini"
+ ],
+ "adeissst": [
+ "assisted",
+ "disseats"
+ ],
+ "aegimnrst": [
+ "emigrants",
+ "germanist",
+ "mastering",
+ "streaming"
+ ],
+ "cehos": [
+ "choes",
+ "chose",
+ "echos"
+ ],
+ "acdeinsty": [
+ "asyndetic",
+ "cystidean",
+ "syndicate"
+ ],
+ "abinoort": [
+ "abortion",
+ "orbation",
+ "robotian"
+ ],
+ "adgilo": [
+ "algoid",
+ "dialog",
+ "goliad"
+ ],
+ "ablst": [
+ "blast",
+ "blats"
+ ],
+ "elop": [
+ "lope",
+ "olpe",
+ "pole"
+ ],
+ "cddeinostu": [
+ "deductions",
+ "discounted"
+ ],
+ "aehrstv": [
+ "harvest",
+ "thraves"
+ ],
+ "ehmorst": [
+ "mothers",
+ "smother",
+ "thermos"
+ ],
+ "acdeiln": [
+ "candiel",
+ "cladine",
+ "decalin",
+ "iceland",
+ "inlaced"
+ ],
+ "acdelns": [
+ "calends",
+ "candles"
+ ],
+ "aadginortu": [
+ "argonautid",
+ "graduation"
+ ],
+ "agiilns": [
+ "aisling",
+ "liasing",
+ "nilgais",
+ "sailing"
+ ],
+ "acders": [
+ "cadres",
+ "cedars",
+ "sacred",
+ "scared"
+ ],
+ "cehmor": [
+ "chomer",
+ "chrome"
+ ],
+ "eilorv": [
+ "lovier",
+ "oliver",
+ "violer",
+ "virole"
+ ],
+ "cgnoo": [
+ "cogon",
+ "congo"
+ ],
+ "egln": [
+ "engl",
+ "genl",
+ "glen",
+ "leng"
+ ],
+ "adelsy": [
+ "delays",
+ "slayed"
+ ],
+ "eilov": [
+ "olive",
+ "ovile",
+ "voile"
+ ],
+ "bcery": [
+ "becry",
+ "bryce"
+ ],
+ "cdeors": [
+ "coders",
+ "credos",
+ "decors",
+ "escrod",
+ "scored"
+ ],
+ "celno": [
+ "clone",
+ "colen"
+ ],
+ "aioss": [
+ "oasis",
+ "ossia",
+ "sosia"
+ ],
+ "abeeilns": [
+ "balinese",
+ "baseline"
+ ],
+ "agnry": [
+ "angry",
+ "rangy"
+ ],
+ "acdeiilnt": [
+ "ctenidial",
+ "identical"
+ ],
+ "abeelst": [
+ "beatles",
+ "besteal",
+ "estable"
+ ],
+ "eeinnortt": [
+ "intertone",
+ "retention"
+ ],
+ "aalmt": [
+ "malta",
+ "talma",
+ "tamal"
+ ],
+ "efrry": [
+ "ferry",
+ "freyr",
+ "fryer",
+ "refry"
+ ],
+ "aeginst": [
+ "easting",
+ "eatings",
+ "gainset",
+ "genista",
+ "ingates",
+ "ingesta",
+ "seating",
+ "signate",
+ "teasing"
+ ],
+ "aahmo": [
+ "haoma",
+ "omaha"
+ ],
+ "eirt": [
+ "iter",
+ "reit",
+ "rite",
+ "teri",
+ "tier",
+ "tire"
+ ],
+ "elmot": [
+ "metol",
+ "molet",
+ "motel"
+ ],
+ "innosu": [
+ "nonius",
+ "unions",
+ "unison"
+ ],
+ "ainrst": [
+ "instar",
+ "santir",
+ "strain",
+ "trains"
+ ],
+ "adgr": [
+ "darg",
+ "drag",
+ "gard",
+ "grad"
+ ],
+ "ehmoosw": [
+ "somehow",
+ "whosome"
+ ],
+ "adeerrst": [
+ "arrested",
+ "retreads",
+ "serrated",
+ "treaders"
+ ],
+ "eipr": [
+ "irpe",
+ "peri",
+ "pier",
+ "prie",
+ "ripe"
+ ],
+ "elry": [
+ "lyre",
+ "rely"
+ ],
+ "acdeiimnost": [
+ "daemonistic",
+ "medications"
+ ],
+ "ceehorrst": [
+ "orchester",
+ "orchestre",
+ "rochester",
+ "torcheres"
+ ],
+ "dginy": [
+ "dying",
+ "dingy"
+ ],
+ "ckstu": [
+ "stuck",
+ "tucks"
+ ],
+ "acgilnp": [
+ "capling",
+ "placing"
+ ],
+ "cefossu": [
+ "focuses",
+ "fucoses"
+ ],
+ "eiostv": [
+ "soviet",
+ "sovite"
+ ],
+ "aertty": [
+ "attery",
+ "yatter",
+ "treaty"
+ ],
+ "aeinrrt": [
+ "arterin",
+ "retrain",
+ "terrain",
+ "trainer"
+ ],
+ "agnor": [
+ "agron",
+ "angor",
+ "argon",
+ "garon",
+ "goran",
+ "grano",
+ "groan",
+ "nagor",
+ "orang",
+ "organ",
+ "rogan",
+ "ronga"
+ ],
+ "aacdensv": [
+ "advances",
+ "canvased"
+ ],
+ "elmno": [
+ "lemon",
+ "melon",
+ "monel"
+ ],
+ "pty": [
+ "pty",
+ "typ"
+ ],
+ "nstu": [
+ "nuts",
+ "stun",
+ "sunt",
+ "tsun",
+ "tuns"
+ ],
+ "ailn": [
+ "alin",
+ "anil",
+ "lain",
+ "lina",
+ "nail"
+ ],
+ "aeinnv": [
+ "avenin",
+ "vienna"
+ ],
+ "anps": [
+ "naps",
+ "pans",
+ "snap",
+ "span"
+ ],
+ "adfnorst": [
+ "forstand",
+ "stanford"
+ ],
+ "aestttu": [
+ "statute",
+ "tautest"
+ ],
+ "acehlp": [
+ "chapel",
+ "lepcha",
+ "pleach"
+ ],
+ "aelrsy": [
+ "layers",
+ "relays",
+ "reslay",
+ "slayer"
+ ],
+ "abceeelrt": [
+ "celebrate",
+ "erectable"
+ ],
+ "cdeemoprss": [
+ "compressed",
+ "decompress"
+ ],
+ "deirr": [
+ "derri",
+ "direr",
+ "drier",
+ "irred",
+ "rider"
+ ],
+ "adirsu": [
+ "darius",
+ "radius"
+ ],
+ "achiinrsst": [
+ "christians",
+ "sinarchist"
+ ],
+ "eeiimprssv": [
+ "impressive",
+ "permissive"
+ ],
+ "ceelrstu": [
+ "cruelest",
+ "lectures"
+ ],
+ "einsw": [
+ "sewin",
+ "sinew",
+ "swine",
+ "wines",
+ "wisen"
+ ],
+ "cpsu": [
+ "cpus",
+ "cups",
+ "cusp",
+ "scup"
+ ],
+ "accdeinst": [
+ "accidents",
+ "desiccant"
+ ],
+ "aceilnoort": [
+ "corelation",
+ "iconolater",
+ "relocation"
+ ],
+ "arsttu": [
+ "astrut",
+ "rattus",
+ "stuart"
+ ],
+ "ail": [
+ "ail",
+ "ila",
+ "lai"
+ ],
+ "emnoor": [
+ "monroe",
+ "mooner",
+ "morone"
+ ],
+ "deenrt": [
+ "denter",
+ "rented",
+ "tender",
+ "tendre",
+ "terned"
+ ],
+ "eeeprrsv": [
+ "perverse",
+ "preserve"
+ ],
+ "emop": [
+ "mope",
+ "poem",
+ "pome"
+ ],
+ "deginnsu": [
+ "undesign",
+ "unsigned",
+ "unsinged"
+ ],
+ "aginsty": [
+ "staying",
+ "stygian"
+ ],
+ "aeerst": [
+ "aretes",
+ "asteer",
+ "easter",
+ "eastre",
+ "eaters",
+ "reseat",
+ "saeter",
+ "seater",
+ "staree",
+ "teaser",
+ "teresa"
+ ],
+ "eehiorst": [
+ "isothere",
+ "theories",
+ "theorise"
+ ],
+ "aeiprs": [
+ "aspire",
+ "paries",
+ "persia",
+ "praise",
+ "sirpea",
+ "spirae",
+ "spirea"
+ ],
+ "ceeinv": [
+ "cevine",
+ "evince",
+ "venice"
+ ],
+ "aeinost": [
+ "aeonist",
+ "asiento",
+ "atonies",
+ "estonia",
+ "satieno"
+ ],
+ "aeenrtv": [
+ "aventre",
+ "nervate",
+ "veteran"
+ ],
+ "adgilnn": [
+ "danglin",
+ "landing"
+ ],
+ "aeikt": [
+ "katie",
+ "keita"
+ ],
+ "aceiilrst": [
+ "clarities",
+ "eristical",
+ "realistic"
+ ],
+ "aelrx": [
+ "laxer",
+ "relax"
+ ],
+ "aeegginnrt": [
+ "generating",
+ "greatening",
+ "renegating"
+ ],
+ "aben": [
+ "bane",
+ "bean",
+ "bena"
+ ],
+ "abenst": [
+ "absent",
+ "basnet",
+ "basten",
+ "besant"
+ ],
+ "acdeoru": [
+ "acuerdo",
+ "cordeau",
+ "ecuador"
+ ],
+ "iiprsst": [
+ "pristis",
+ "spirits",
+ "tripsis"
+ ],
+ "aflot": [
+ "aloft",
+ "float",
+ "flota"
+ ],
+ "cilno": [
+ "colin",
+ "conli",
+ "nicol"
+ ],
+ "abis": [
+ "absi",
+ "bais",
+ "bias",
+ "isba"
+ ],
+ "ahpst": [
+ "paths",
+ "spath",
+ "staph"
+ ],
+ "beinrtu": [
+ "tribune",
+ "tuberin",
+ "turbine"
+ ],
+ "eelssv": [
+ "selves",
+ "vessel"
+ ],
+ "acdis": [
+ "acids",
+ "asdic",
+ "cadis",
+ "caids",
+ "sadic"
+ ],
+ "eirssuv": [
+ "servius",
+ "survise",
+ "viruses"
+ ],
+ "aceehpr": [
+ "cheaper",
+ "peacher"
+ ],
+ "adimt": [
+ "admit",
+ "atmid"
+ ],
+ "emm": [
+ "emm",
+ "mem"
+ ],
+ "aamos": [
+ "omasa",
+ "samoa"
+ ],
+ "aceinorst": [
+ "atroscine",
+ "certosina",
+ "creations",
+ "narcotise",
+ "ostracine",
+ "reactions",
+ "secration",
+ "tinoceras",
+ "tricosane"
+ ],
+ "aegilns": [
+ "inglesa",
+ "leasing",
+ "linages",
+ "sealing"
+ ],
+ "aelnru": [
+ "alrune",
+ "lunare",
+ "neural",
+ "ulnare",
+ "unreal"
+ ],
+ "adqsu": [
+ "quads",
+ "squad"
+ ],
+ "aeelrt": [
+ "earlet",
+ "elater",
+ "relate"
+ ],
+ "aegsw": [
+ "swage",
+ "wages"
+ ],
+ "effrsu": [
+ "ruffes",
+ "suffer"
+ ],
+ "eforsst": [
+ "forests",
+ "fosters"
+ ],
+ "anno": [
+ "anno",
+ "anon",
+ "nona",
+ "onan"
+ ],
+ "aailmrt": [
+ "marital",
+ "martial"
+ ],
+ "aeinrt": [
+ "anteri",
+ "entria",
+ "nerita",
+ "ratine",
+ "retain",
+ "retina",
+ "tanier"
+ ],
+ "elnntu": [
+ "nunlet",
+ "tunnel",
+ "unlent"
+ ],
+ "eegnrs": [
+ "genres",
+ "greens"
+ ],
+ "aenpstt": [
+ "patents",
+ "pattens"
+ ],
+ "achos": [
+ "chaos",
+ "oshac"
+ ],
+ "aehtw": [
+ "awhet",
+ "wheat"
+ ],
+ "adeginrs": [
+ "deraigns",
+ "disrange",
+ "gradines",
+ "readings"
+ ],
+ "ceilmopr": [
+ "compiler",
+ "complier"
+ ],
+ "abess": [
+ "bases",
+ "sabes"
+ ],
+ "accdesu": [
+ "accused",
+ "succade"
+ ],
+ "dlou": [
+ "loud",
+ "ludo"
+ ],
+ "bdeir": [
+ "bider",
+ "birde",
+ "bredi",
+ "bride",
+ "rebid"
+ ],
+ "aceinnsst": [
+ "anticness",
+ "cantiness",
+ "incessant",
+ "instances"
+ ],
+ "achnor": [
+ "anchor",
+ "archon",
+ "charon",
+ "rancho"
+ ],
+ "astt": [
+ "stat",
+ "tats"
+ ],
+ "eessx": [
+ "essex",
+ "sexes"
+ ],
+ "abceehs": [
+ "beaches",
+ "bechase"
+ ],
+ "deflors": [
+ "folders",
+ "refolds"
+ ],
+ "eorrstu": [
+ "rouster",
+ "routers",
+ "tourers",
+ "trouser"
+ ],
+ "bios": [
+ "bios",
+ "bois",
+ "obis"
+ ],
+ "cmp": [
+ "cpm",
+ "pcm"
+ ],
+ "eeht": [
+ "hete",
+ "thee"
+ ],
+ "eopp": [
+ "epop",
+ "pepo",
+ "pope"
+ ],
+ "adlnor": [
+ "androl",
+ "arnold",
+ "ladron",
+ "lardon",
+ "lordan",
+ "roland",
+ "ronald"
+ ],
+ "cchiks": [
+ "chicks",
+ "schick"
+ ],
+ "aceltt": [
+ "cattle",
+ "tectal"
+ ],
+ "abm": [
+ "amb",
+ "bam",
+ "mab"
+ ],
+ "aacdilr": [
+ "cardial",
+ "radical"
+ ],
+ "aeerrstu": [
+ "austerer",
+ "treasure"
+ ],
+ "adelor": [
+ "loader",
+ "ordeal",
+ "reload"
+ ],
+ "aeflm": [
+ "flame",
+ "fleam"
+ ],
+ "aknst": [
+ "stank",
+ "tanks"
+ ],
+ "aemnorty": [
+ "myronate",
+ "monetary",
+ "naometry"
+ ],
+ "aceilprst": [
+ "palestric",
+ "particles"
+ ],
+ "acdeinoort": [
+ "aerodontic",
+ "carotenoid",
+ "coordinate",
+ "coronadite",
+ "decoration"
+ ],
+ "aiktuw": [
+ "kuwait",
+ "waukit"
+ ],
+ "eilmy": [
+ "elymi",
+ "emily",
+ "limey"
+ ],
+ "aiiilmnott": [
+ "limitation",
+ "militation"
+ ],
+ "ceilmop": [
+ "compile",
+ "polemic"
+ ],
+ "beerstw": [
+ "bestrew",
+ "webster"
+ ],
+ "abdilr": [
+ "bildar",
+ "bridal",
+ "labrid",
+ "libard",
+ "ribald"
+ ],
+ "agm": [
+ "gam",
+ "mag"
+ ],
+ "efghirt": [
+ "fighter",
+ "freight",
+ "refight"
+ ],
+ "abeert": [
+ "beater",
+ "berate",
+ "betear",
+ "rebate",
+ "rebeat"
+ ],
+ "adnsu": [
+ "sudan",
+ "unsad"
+ ],
+ "cer": [
+ "cre",
+ "rec"
+ ],
+ "aceforsst": [
+ "factoress",
+ "forecasts"
+ ],
+ "eekn": [
+ "keen",
+ "knee"
+ ],
+ "eppr": [
+ "perp",
+ "prep",
+ "repp"
+ ],
+ "cehm": [
+ "chem",
+ "mech"
+ ],
+ "aefsstt": [
+ "fastest",
+ "setfast"
+ ],
+ "belrtu": [
+ "bulter",
+ "burlet",
+ "butler",
+ "turble"
+ ],
+ "acdeginort": [
+ "centigrado",
+ "decorating"
+ ],
+ "allopry": [
+ "payroll",
+ "polarly"
+ ],
+ "hinst": [
+ "hints",
+ "thins",
+ "thisn"
+ ],
+ "acellops": [
+ "collapse",
+ "escallop"
+ ],
+ "aaceimrs": [
+ "americas",
+ "cramasie",
+ "mesaraic"
+ ],
+ "oprs": [
+ "pros",
+ "spor"
+ ],
+ "aailtv": [
+ "avital",
+ "latvia"
+ ],
+ "aelrry": [
+ "rarely",
+ "rearly"
+ ],
+ "aahmrt": [
+ "amarth",
+ "martha",
+ "matrah"
+ ],
+ "eeginss": [
+ "genesis",
+ "seeings"
+ ],
+ "aegru": [
+ "argue",
+ "auger",
+ "gaure",
+ "rugae"
+ ],
+ "aelmst": [
+ "lamest",
+ "metals",
+ "samlet"
+ ],
+ "egilntt": [
+ "ettling",
+ "letting"
+ ],
+ "cir": [
+ "cir",
+ "ric"
+ ],
+ "aeijm": [
+ "jaime",
+ "jamie"
+ ],
+ "aceilprt": [
+ "particle",
+ "plicater",
+ "prelatic"
+ ],
+ "ceeinopprt": [
+ "perception",
+ "preception"
+ ],
+ "aeilmnrs": [
+ "marlines",
+ "minerals",
+ "mislearn"
+ ],
+ "adeisv": [
+ "advise",
+ "davies",
+ "visaed"
+ ],
+ "belostt": [
+ "bottles",
+ "setbolt"
+ ],
+ "aaceeinnrss": [
+ "necessarian",
+ "renaissance"
+ ],
+ "aars": [
+ "rasa",
+ "sara"
+ ],
+ "acdeinnor": [
+ "cerdonian",
+ "encardion",
+ "ordinance"
+ ],
+ "eghhsu": [
+ "heughs",
+ "hughes",
+ "sheugh"
+ ],
+ "eeffjry": [
+ "jeffery",
+ "jeffrey"
+ ],
+ "aeeoprst": [
+ "asterope",
+ "operates",
+ "protease"
+ ],
+ "acors": [
+ "arcos",
+ "crosa",
+ "orcas",
+ "oscar",
+ "sacro"
+ ],
+ "emnsu": [
+ "menus",
+ "neums",
+ "sumen"
+ ],
+ "aeelrv": [
+ "laveer",
+ "leaver",
+ "reveal",
+ "vealer"
+ ],
+ "aimno": [
+ "amino",
+ "animo",
+ "inoma",
+ "naomi",
+ "omani",
+ "omina"
+ ],
+ "cciilns": [
+ "cinclis",
+ "clinics"
+ ],
+ "lms": [
+ "msl",
+ "sml"
+ ],
+ "cow": [
+ "cow",
+ "cwo"
+ ],
+ "gilny": [
+ "lying",
+ "lingy"
+ ],
+ "deiv": [
+ "devi",
+ "dive",
+ "vide",
+ "vied"
+ ],
+ "abnry": [
+ "barny",
+ "bryan"
+ ],
+ "eoprstt": [
+ "potters",
+ "protest",
+ "spotter"
+ ],
+ "eirst": [
+ "iters",
+ "reist",
+ "resit",
+ "rites",
+ "steri",
+ "stire",
+ "tiers",
+ "tires",
+ "tries"
+ ],
+ "aghinsw": [
+ "hawsing",
+ "shawing",
+ "washing"
+ ],
+ "celorsu": [
+ "closure",
+ "colures"
+ ],
+ "adir": [
+ "arid",
+ "dari",
+ "raid"
+ ],
+ "beimrt": [
+ "betrim",
+ "timber",
+ "timbre"
+ ],
+ "eeinnst": [
+ "ensient",
+ "intense",
+ "sennite",
+ "sentine"
+ ],
+ "ehorssw": [
+ "reshows",
+ "showers"
+ ],
+ "gilnru": [
+ "luring",
+ "ruling",
+ "urling"
+ ],
+ "dirt": [
+ "dirt",
+ "trid"
+ ],
+ "doprs": [
+ "dorps",
+ "drops",
+ "prods",
+ "sprod"
+ ],
+ "gilnpsu": [
+ "pulings",
+ "pulsing"
+ ],
+ "deellnor": [
+ "enrolled",
+ "rondelle"
+ ],
+ "cersw": [
+ "crews",
+ "screw"
+ ],
+ "eiimnrsst": [
+ "ministers",
+ "misinters"
+ ],
+ "abelm": [
+ "amble",
+ "belam",
+ "blame",
+ "mabel",
+ "melba"
+ ],
+ "aeegnv": [
+ "avenge",
+ "geneva",
+ "vangee"
+ ],
+ "dist": [
+ "dist",
+ "dits",
+ "stid"
+ ],
+ "addehn": [
+ "hadden",
+ "handed"
+ ],
+ "aeiknt": [
+ "intake",
+ "kentia",
+ "tankie"
+ ],
+ "afilmnor": [
+ "formalin",
+ "formnail",
+ "informal",
+ "laniform"
+ ],
+ "accehimns": [
+ "mechanics",
+ "mischance"
+ ],
+ "ffity": [
+ "fifty",
+ "tiffy"
+ ],
+ "adeehrs": [
+ "adheres",
+ "headers",
+ "hearsed",
+ "sheared"
+ ],
+ "aceilmnru": [
+ "ceruminal",
+ "melanuric",
+ "numerical"
+ ],
+ "aerssu": [
+ "assure",
+ "urases"
+ ],
+ "dimosu": [
+ "modius",
+ "odiums",
+ "sodium"
+ ],
+ "ehmnoor": [
+ "hormone",
+ "moorhen"
+ ],
+ "gipr": [
+ "grip",
+ "prig"
+ ],
+ "aalnv": [
+ "alvan",
+ "naval"
+ ],
+ "aceilnopr": [
+ "oliprance",
+ "porcelain"
+ ],
+ "bdegirs": [
+ "begirds",
+ "bridges"
+ ],
+ "attw": [
+ "twat",
+ "watt"
+ ],
+ "cdeent": [
+ "cedent",
+ "decent",
+ "decnet"
+ ],
+ "acginst": [
+ "actings",
+ "casting"
+ ],
+ "adnoty": [
+ "adyton",
+ "dayton"
+ ],
+ "aclors": [
+ "carlos",
+ "carols",
+ "claros",
+ "corals"
+ ],
+ "enor": [
+ "nore",
+ "oner",
+ "reno",
+ "rone"
+ ],
+ "adnno": [
+ "donna",
+ "nonda"
+ ],
+ "abcin": [
+ "bacin",
+ "cabin"
+ ],
+ "acginnns": [
+ "cannings",
+ "scanning"
+ ],
+ "ailmuv": [
+ "maulvi",
+ "valium"
+ ],
+ "cdelorss": [
+ "cordless",
+ "scolders"
+ ],
+ "defir": [
+ "fired",
+ "fried"
+ ],
+ "airsy": [
+ "sairy",
+ "syria"
+ ],
+ "eikln": [
+ "inkle",
+ "liken"
+ ],
+ "glos": [
+ "glos",
+ "logs",
+ "slog"
+ ],
+ "eorrt": [
+ "retro",
+ "roter"
+ ],
+ "elo": [
+ "leo",
+ "loe",
+ "ole"
+ ],
+ "accilrru": [
+ "circular",
+ "curricla"
+ ],
+ "isstu": [
+ "situs",
+ "suist",
+ "suits",
+ "tissu"
+ ],
+ "agr": [
+ "agr",
+ "arg",
+ "gar",
+ "gra",
+ "rag"
+ ],
+ "abeirrz": [
+ "bizarre",
+ "brazier"
+ ],
+ "ego": [
+ "ego",
+ "geo"
+ ],
+ "bbinor": [
+ "ribbon",
+ "robbin"
+ ],
+ "deo": [
+ "doe",
+ "edo",
+ "ode"
+ ],
+ "aprsttu": [
+ "startup",
+ "upstart"
+ ],
+ "ait": [
+ "ait",
+ "ati",
+ "ita",
+ "tai"
+ ],
+ "giiknss": [
+ "kissing",
+ "skiings"
+ ],
+ "adhny": [
+ "anhyd",
+ "haydn",
+ "handy"
+ ],
+ "apsw": [
+ "paws",
+ "swap",
+ "waps",
+ "wasp"
+ ],
+ "eegmorty": [
+ "geoemtry",
+ "geometry"
+ ],
+ "abs": [
+ "abs",
+ "asb",
+ "bas",
+ "sab"
+ ],
+ "ims": [
+ "ism",
+ "mis",
+ "sim"
+ ],
+ "dehiss": [
+ "dishes",
+ "hissed"
+ ],
+ "aceilpr": [
+ "caliper",
+ "earclip",
+ "picarel",
+ "replica"
+ ],
+ "beirt": [
+ "biter",
+ "brite",
+ "tiber",
+ "tribe"
+ ],
+ "aderst": [
+ "daters",
+ "derats",
+ "stader",
+ "stared",
+ "sterad",
+ "strade",
+ "trades",
+ "treads"
+ ],
+ "eknu": [
+ "neuk",
+ "nuke"
+ ],
+ "aclm": [
+ "calm",
+ "clam"
+ ],
+ "cdeiop": [
+ "copied",
+ "epodic"
+ ],
+ "aciost": [
+ "coatis",
+ "isotac",
+ "scotia"
+ ],
+ "afgimnr": [
+ "farming",
+ "fingram",
+ "framing"
+ ],
+ "bginos": [
+ "bingos",
+ "gibson",
+ "obsign"
+ ],
+ "orty": [
+ "ryot",
+ "royt",
+ "tyro",
+ "tory",
+ "troy"
+ ],
+ "ellorr": [
+ "reroll",
+ "roller"
+ ],
+ "aeginorz": [
+ "agonizer",
+ "orangize",
+ "organize"
+ ],
+ "aacdeeipprt": [
+ "appreciated",
+ "appredicate"
+ ],
+ "ceilno": [
+ "cineol",
+ "clione",
+ "cloine",
+ "coelin",
+ "encoil",
+ "enolic"
+ ],
+ "ailnot": [
+ "italon",
+ "latino",
+ "lation",
+ "talion"
+ ],
+ "aaghn": [
+ "aghan",
+ "ghana"
+ ],
+ "deegs": [
+ "edges",
+ "sedge"
+ ],
+ "adehlns": [
+ "handles",
+ "handsel"
+ ],
+ "deiklls": [
+ "deskill",
+ "dillesk",
+ "skilled"
+ ],
+ "aahmst": [
+ "amsath",
+ "asthma"
+ ],
+ "ipr": [
+ "ipr",
+ "pir",
+ "rip"
+ ],
+ "aderrw": [
+ "drawer",
+ "redraw",
+ "reward",
+ "warder",
+ "warred"
+ ],
+ "arty": [
+ "arty",
+ "atry",
+ "tray"
+ ],
+ "inpstu": [
+ "inputs",
+ "ptinus",
+ "unspit"
+ ],
+ "aaeinrrstw": [
+ "warranties",
+ "warrantise"
+ ],
+ "adelm": [
+ "demal",
+ "lamed",
+ "medal"
+ ],
+ "aklsw": [
+ "lawks",
+ "walks"
+ ],
+ "bhoot": [
+ "bhoot",
+ "booth"
+ ],
+ "cdeeeflrt": [
+ "redeflect",
+ "reflected"
+ ],
+ "benos": [
+ "bones",
+ "ebons"
+ ],
+ "bdeer": [
+ "brede",
+ "breed",
+ "rebed"
+ ],
+ "cdeeortt": [
+ "cottered",
+ "detector"
+ ],
+ "deginor": [
+ "eroding",
+ "gironde",
+ "groined",
+ "ignored",
+ "negroid",
+ "redoing"
+ ],
+ "alopr": [
+ "parol",
+ "polar",
+ "poral",
+ "proal"
+ ],
+ "aaegilntuv": [
+ "evaluating",
+ "vaginulate"
+ ],
+ "ilp": [
+ "ipl",
+ "lip",
+ "pil",
+ "pli"
+ ],
+ "aeghrt": [
+ "gareth",
+ "gather"
+ ],
+ "adeflr": [
+ "alfred",
+ "fardel",
+ "flared"
+ ],
+ "acery": [
+ "carey",
+ "craye"
+ ],
+ "elmost": [
+ "molest",
+ "motels"
+ ],
+ "cdeeoprs": [
+ "procedes",
+ "proceeds"
+ ],
+ "cdeiinrt": [
+ "indicter",
+ "indirect",
+ "reindict"
+ ],
+ "aerrst": [
+ "arrest",
+ "astrer",
+ "rarest",
+ "raster",
+ "raters",
+ "starer",
+ "tarres",
+ "terras",
+ "treasr"
+ ],
+ "deelpy": [
+ "deeply",
+ "yelped"
+ ],
+ "cit": [
+ "cit",
+ "tic"
+ ],
+ "aaimnr": [
+ "airman",
+ "amarin",
+ "marian",
+ "marina",
+ "mirana"
+ ],
+ "abinos": [
+ "basion",
+ "bonsai",
+ "sabino"
+ ],
+ "aiopt": [
+ "patio",
+ "taipo",
+ "topia"
+ ],
+ "aceelnort": [
+ "antrocele",
+ "coeternal",
+ "tolerance"
+ ],
+ "aceiirttvy": [
+ "creativity",
+ "reactivity"
+ ],
+ "dlloy": [
+ "dolly",
+ "lloyd"
+ ],
+ "deey": [
+ "eyed",
+ "yede"
+ ],
+ "abgr": [
+ "brag",
+ "garb",
+ "grab"
+ ],
+ "ceinoprst": [
+ "inceptors",
+ "inspector"
+ ],
+ "abens": [
+ "banes",
+ "beans",
+ "besan"
+ ],
+ "eills": [
+ "liles",
+ "lisle",
+ "selli"
+ ],
+ "aekns": [
+ "kanes",
+ "skean",
+ "snake",
+ "sneak"
+ ],
+ "adelnor": [
+ "endoral",
+ "ladrone",
+ "leonard"
+ ],
+ "ailnps": [
+ "lapins",
+ "plains",
+ "spinal"
+ ],
+ "admnory": [
+ "doryman",
+ "raymond"
+ ],
+ "adeiiintt": [
+ "dietitian",
+ "initiated"
+ ],
+ "floo": [
+ "fool",
+ "loof",
+ "olof"
+ ],
+ "aacelnrst": [
+ "ancestral",
+ "lancaster"
+ ],
+ "beeorsv": [
+ "observe",
+ "obverse",
+ "verbose"
+ ],
+ "aceinnorst": [
+ "containers",
+ "resanction",
+ "sanctioner"
+ ],
+ "aklr": [
+ "karl",
+ "kral",
+ "lark"
+ ],
+ "aacilr": [
+ "alaric",
+ "racial"
+ ],
+ "aeeginrtt": [
+ "argentite",
+ "grenatite",
+ "integrate"
+ ],
+ "abdemru": [
+ "bermuda",
+ "rumbaed"
+ ],
+ "aaadmn": [
+ "amadan",
+ "amanda",
+ "manada"
+ ],
+ "beilmos": [
+ "mobiles",
+ "obelism"
+ ],
+ "aceerrt": [
+ "caterer",
+ "recrate",
+ "retrace",
+ "terrace"
+ ],
+ "deeilpr": [
+ "periled",
+ "replied"
+ ],
+ "elnosv": [
+ "novels",
+ "sloven",
+ "volens"
+ ],
+ "aijl": [
+ "jail",
+ "lija"
+ ],
+ "aeflsy": [
+ "fayles",
+ "safely"
+ ],
+ "deikny": [
+ "dinkey",
+ "kidney"
+ ],
+ "denss": [
+ "sends",
+ "sneds"
+ ],
+ "abdelru": [
+ "delubra",
+ "durable"
+ ],
+ "horstw": [
+ "rowths",
+ "throws",
+ "whorts",
+ "worths"
+ ],
+ "eimorstu": [
+ "moisture",
+ "semitour"
+ ],
+ "eimrt": [
+ "ermit",
+ "merit",
+ "miter",
+ "mitre",
+ "mtier",
+ "remit",
+ "timer"
+ ],
+ "abelstt": [
+ "battels",
+ "battles",
+ "tablets"
+ ],
+ "acdeorstu": [
+ "aeroducts",
+ "ceratodus",
+ "croustade",
+ "educators"
+ ],
+ "aegilmnnt": [
+ "alignment",
+ "lamenting"
+ ],
+ "aey": [
+ "aye",
+ "yea"
+ ],
+ "eorstw": [
+ "restow",
+ "stower",
+ "towers",
+ "towser",
+ "worset"
+ ],
+ "ackrs": [
+ "carks",
+ "racks"
+ ],
+ "acel": [
+ "acle",
+ "alce",
+ "alec",
+ "lace"
+ ],
+ "ansty": [
+ "antsy",
+ "nasty",
+ "santy",
+ "styan",
+ "tansy"
+ ],
+ "adeilttu": [
+ "altitude",
+ "latitude"
+ ],
+ "gluy": [
+ "guly",
+ "ugly"
+ ],
+ "deiopsst": [
+ "deposits",
+ "topsides"
+ ],
+ "asttw": [
+ "twats",
+ "watts"
+ ],
+ "ahrt": [
+ "hart",
+ "rath",
+ "tahr",
+ "thar",
+ "trah"
+ ],
+ "abdenrr": [
+ "bernard",
+ "brander",
+ "rebrand"
+ ],
+ "bestu": [
+ "stube",
+ "subet",
+ "tubes"
+ ],
+ "clo": [
+ "clo",
+ "col",
+ "loc"
+ ],
+ "eiprst": [
+ "esprit",
+ "priest",
+ "pteris",
+ "ripest",
+ "sitrep",
+ "sprite",
+ "stripe",
+ "tripes"
+ ],
+ "dfloy": [
+ "floyd",
+ "foldy"
+ ],
+ "acenrt": [
+ "canter",
+ "carnet",
+ "centra",
+ "cranet",
+ "creant",
+ "cretan",
+ "nectar",
+ "recant",
+ "tanrec",
+ "trance"
+ ],
+ "achilnos": [
+ "lichanos",
+ "nicholas"
+ ],
+ "bilo": [
+ "bilo",
+ "biol",
+ "boil",
+ "lobi",
+ "obli"
+ ],
+ "bdelnu": [
+ "bundle",
+ "unbled"
+ ],
+ "iknss": [
+ "sinks",
+ "skins"
+ ],
+ "adeilm": [
+ "aldime",
+ "mailed",
+ "medial"
+ ],
+ "aderrsw": [
+ "drawers",
+ "redraws",
+ "resward",
+ "rewards",
+ "warders"
+ ],
+ "ddeefn": [
+ "defend",
+ "fended"
+ ],
+ "acddeiim": [
+ "mediacid",
+ "medicaid"
+ ],
+ "eort": [
+ "rote",
+ "tore"
+ ],
+ "ehlsw": [
+ "welsh",
+ "whsle"
+ ],
+ "elnost": [
+ "lentos",
+ "solent",
+ "stolen",
+ "telson"
+ ],
+ "aci": [
+ "cai",
+ "cia"
+ ],
+ "deeeimnrst": [
+ "densimeter",
+ "determines",
+ "misentered"
+ ],
+ "lopy": [
+ "ploy",
+ "poly"
+ ],
+ "aers": [
+ "ares",
+ "arse",
+ "ears",
+ "eras",
+ "rase",
+ "sare",
+ "sear",
+ "sera"
+ ],
+ "aalnrstu": [
+ "naturals",
+ "saturnal"
+ ],
+ "deelnrs": [
+ "lenders",
+ "relends",
+ "slender"
+ ],
+ "eemr": [
+ "emer",
+ "erme",
+ "meer",
+ "mere",
+ "reem"
+ ],
+ "aeegrs": [
+ "agrees",
+ "eagers",
+ "eagres",
+ "grease",
+ "ragees",
+ "sageer"
+ ],
+ "ceehrs": [
+ "cheers",
+ "creesh"
+ ],
+ "dgi": [
+ "dig",
+ "gid"
+ ],
+ "ehimnnpstu": [
+ "punishment",
+ "unshipment"
+ ],
+ "aceinooprrt": [
+ "incorporate",
+ "procreation"
+ ],
+ "aeerrrstu": [
+ "serrature",
+ "treasurer"
+ ],
+ "ceeenss": [
+ "essence",
+ "necesse",
+ "senesce"
+ ],
+ "ehlmos": [
+ "holmes",
+ "mohels"
+ ],
+ "gis": [
+ "gis",
+ "sig"
+ ],
+ "ceehrst": [
+ "chester",
+ "etchers",
+ "retches",
+ "tresche"
+ ],
+ "isttw": [
+ "twist",
+ "twits"
+ ],
+ "einstu": [
+ "intuse",
+ "tenuis",
+ "unites",
+ "unties"
+ ],
+ "bdeiru": [
+ "burdie",
+ "buried",
+ "rubied"
+ ],
+ "adinrw": [
+ "darwin",
+ "inward"
+ ],
+ "aknrs": [
+ "karns",
+ "knars",
+ "krans",
+ "narks",
+ "ranks",
+ "snark"
+ ],
+ "bdetu": [
+ "debut",
+ "tubed"
+ ],
+ "abdelry": [
+ "bradley",
+ "dryable"
+ ],
+ "deny": [
+ "deny",
+ "dyne"
+ ],
+ "abil": [
+ "albi",
+ "bail",
+ "bali"
+ ],
+ "iort": [
+ "riot",
+ "roit",
+ "roti",
+ "tiro",
+ "tori",
+ "trio"
+ ],
+ "aekmrr": [
+ "marker",
+ "remark"
+ ],
+ "grsu": [
+ "grus",
+ "rugs",
+ "surg"
+ ],
+ "aadnrs": [
+ "nasard",
+ "sandra"
+ ],
+ "acmnoo": [
+ "mocoan",
+ "monaco"
+ ],
+ "aeeimrst": [
+ "emirates",
+ "steamier"
+ ],
+ "aeft": [
+ "atef",
+ "fate",
+ "feat",
+ "feta"
+ ],
+ "borstu": [
+ "robust",
+ "turbos"
+ ],
+ "agrs": [
+ "gars",
+ "gras",
+ "rags"
+ ],
+ "ddeenoprs": [
+ "desponder",
+ "responded"
+ ],
+ "imr": [
+ "mir",
+ "rim"
+ ],
+ "aeilnp": [
+ "alpine",
+ "nepali",
+ "penial",
+ "pineal"
+ ],
+ "dis": [
+ "dis",
+ "ids",
+ "sid"
+ ],
+ "eimrx": [
+ "mirex",
+ "mixer",
+ "remix"
+ ],
+ "eenrw": [
+ "newer",
+ "renew",
+ "weren"
+ ],
+ "aky": [
+ "yak",
+ "kay"
+ ],
+ "ceips": [
+ "epics",
+ "sepic",
+ "spice"
+ ],
+ "amos": [
+ "amos",
+ "moas",
+ "soam",
+ "soma"
+ ],
+ "celoor": [
+ "cooler",
+ "recool"
+ ],
+ "aciis": [
+ "ascii",
+ "isiac"
+ ],
+ "bdeeginr": [
+ "beringed",
+ "breeding"
+ ],
+ "aciinostt": [
+ "actionist",
+ "citations"
+ ],
+ "dnoor": [
+ "donor",
+ "rondo"
+ ],
+ "einnost": [
+ "intones",
+ "stenion",
+ "tension"
+ ],
+ "ahrst": [
+ "harst",
+ "harts",
+ "tahrs",
+ "trash"
+ ],
+ "aehpss": [
+ "pashes",
+ "phases",
+ "shapes"
+ ],
+ "eeelnopv": [
+ "envelope",
+ "ovenpeel"
+ ],
+ "adein": [
+ "diane",
+ "endia",
+ "idean"
+ ],
+ "bdeers": [
+ "bredes",
+ "breeds"
+ ],
+ "adiprs": [
+ "dispar",
+ "rapids",
+ "sparid"
+ ],
+ "cdios": [
+ "disco",
+ "sodic"
+ ],
+ "defin": [
+ "fiend",
+ "fined",
+ "indef"
+ ],
+ "eimnoost": [
+ "emotions",
+ "mooniest"
+ ],
+ "aceelnrs": [
+ "cleaners",
+ "cleanser",
+ "recleans"
+ ],
+ "aabgilnru": [
+ "biangular",
+ "bulgarian"
+ ],
+ "aeelnrt": [
+ "alterne",
+ "enteral",
+ "eternal",
+ "teleran",
+ "teneral"
+ ],
+ "acehirss": [
+ "cashiers",
+ "rachises"
+ ],
+ "agmu": [
+ "gaum",
+ "guam",
+ "muga"
+ ],
+ "ceit": [
+ "ceti",
+ "cite",
+ "tice"
+ ],
+ "gip": [
+ "gip",
+ "pig"
+ ],
+ "imnsu": [
+ "minus",
+ "numis",
+ "unism"
+ ],
+ "aeeilnpst": [
+ "palestine",
+ "penalties",
+ "tapelines"
+ ],
+ "aaeimnr": [
+ "amarine",
+ "armenia"
+ ],
+ "celosst": [
+ "closest",
+ "closets"
+ ],
+ "aacdeittv": [
+ "activated",
+ "cavitated"
+ ],
+ "acersst": [
+ "actress",
+ "casters",
+ "recasts"
+ ],
+ "ilt": [
+ "lit",
+ "til"
+ ],
+ "deilss": [
+ "dessil",
+ "sidles",
+ "slides"
+ ],
+ "ailmn": [
+ "lamin",
+ "liman",
+ "milan"
+ ],
+ "deelnr": [
+ "eldern",
+ "lender",
+ "relend"
+ ],
+ "chorsu": [
+ "chorus",
+ "urochs"
+ ],
+ "cehiinrst": [
+ "christine",
+ "snitchier"
+ ],
+ "adegru": [
+ "argued",
+ "dargue"
+ ],
+ "hmnopsyy": [
+ "physnomy",
+ "symphony"
+ ],
+ "aceklr": [
+ "calker",
+ "clarke",
+ "lacker",
+ "rackle",
+ "recalk",
+ "reckla"
+ ],
+ "ilnos": [
+ "insol",
+ "isoln",
+ "linos",
+ "lions",
+ "loins",
+ "noils"
+ ],
+ "loops": [
+ "loops",
+ "polos",
+ "pools",
+ "sloop",
+ "spool"
+ ],
+ "cilry": [
+ "cyril",
+ "lyric"
+ ],
+ "aceilr": [
+ "acerli",
+ "carlie",
+ "claire",
+ "eclair",
+ "erical",
+ "lacier"
+ ],
+ "eopr": [
+ "pore",
+ "rope"
+ ],
+ "aailnort": [
+ "notarial",
+ "rational",
+ "rotalian"
+ ],
+ "efghirst": [
+ "fighters",
+ "freights",
+ "refights"
+ ],
+ "abcehmrs": [
+ "becharms",
+ "brechams",
+ "chambers"
+ ],
+ "ceeilmnopt": [
+ "emplection",
+ "incomplete"
+ ],
+ "benrru": [
+ "burner",
+ "reburn"
+ ],
+ "rsy": [
+ "yrs",
+ "syr"
+ ],
+ "foo": [
+ "foo",
+ "ofo",
+ "oof"
+ ],
+ "eeglnt": [
+ "gentle",
+ "telegn"
+ ],
+ "deeepr": [
+ "deeper",
+ "peered"
+ ],
+ "hortwy": [
+ "worthy",
+ "wrothy"
+ ],
+ "ainsst": [
+ "saints",
+ "satins",
+ "stains"
+ ],
+ "aceirrs": [
+ "carries",
+ "scarier"
+ ],
+ "dou": [
+ "duo",
+ "oud",
+ "udo"
+ ],
+ "denov": [
+ "devon",
+ "doven"
+ ],
+ "aeehln": [
+ "anhele",
+ "helena"
+ ],
+ "aessv": [
+ "saves",
+ "vases"
+ ],
+ "addeegrr": [
+ "degrader",
+ "regarded",
+ "regraded"
+ ],
+ "cdeeenptux": [
+ "unexcepted",
+ "unexpected"
+ ],
+ "aimnor": [
+ "mainor",
+ "manoir",
+ "marion",
+ "minora",
+ "morian",
+ "romain"
+ ],
+ "deilnotu": [
+ "outlined",
+ "untoiled"
+ ],
+ "aeginrtt": [
+ "gnattier",
+ "treating"
+ ],
+ "rst": [
+ "str",
+ "trs"
+ ],
+ "aaeinrrtv": [
+ "narrative",
+ "veratrina"
+ ],
+ "emnoorsu": [
+ "enormous",
+ "unmorose"
+ ],
+ "aakmr": [
+ "karma",
+ "krama",
+ "makar",
+ "marka"
+ ],
+ "cinosst": [
+ "consist",
+ "tocsins"
+ ],
+ "aclsu": [
+ "cauls",
+ "claus",
+ "scaul"
+ ],
+ "beirst": [
+ "bestir",
+ "bister",
+ "bistre",
+ "biters",
+ "bitser",
+ "tribes"
+ ],
+ "adimr": [
+ "mardi",
+ "marid"
+ ],
+ "abceinst": [
+ "bascinet",
+ "cabinets"
+ ],
+ "aehks": [
+ "hakes",
+ "shake"
+ ],
+ "aabeglr": [
+ "algebar",
+ "algebra"
+ ],
+ "illsy": [
+ "yills",
+ "silyl",
+ "silly",
+ "slily"
+ ],
+ "einrssu": [
+ "insures",
+ "rusines",
+ "russine",
+ "serinus",
+ "sunrise"
+ ],
+ "fru": [
+ "fur",
+ "urf"
+ ],
+ "eeiilmnt": [
+ "ilmenite",
+ "melinite",
+ "menilite"
+ ],
+ "adeilry": [
+ "arylide",
+ "readily"
+ ],
+ "cos": [
+ "cos",
+ "osc",
+ "soc"
+ ],
+ "dinstu": [
+ "dustin",
+ "nudist"
+ ],
+ "aadin": [
+ "andia",
+ "danai",
+ "diana",
+ "naiad"
+ ],
+ "eenrssu": [
+ "ensures",
+ "russene"
+ ],
+ "aeeilrstv": [
+ "levirates",
+ "relatives",
+ "versatile"
+ ],
+ "adilnsy": [
+ "islandy",
+ "lindsay"
+ ],
+ "aehms": [
+ "ahems",
+ "haems",
+ "hames",
+ "sahme",
+ "shame",
+ "shema"
+ ],
+ "eehlnopty": [
+ "polythene",
+ "telephony"
+ ],
+ "aaflt": [
+ "aflat",
+ "fatal"
+ ],
+ "aelorrst": [
+ "realtors",
+ "relators",
+ "restoral"
+ ],
+ "abeghinrt": [
+ "breathing",
+ "rebathing"
+ ],
+ "aacghilpr": [
+ "algraphic",
+ "graphical"
+ ],
+ "aeerrtt": [
+ "ettarre",
+ "retreat",
+ "treater"
+ ],
+ "abelry": [
+ "barely",
+ "barley",
+ "bleary",
+ "yerbal"
+ ],
+ "gru": [
+ "gur",
+ "rug"
+ ],
+ "aaiimnnst": [
+ "amanitins",
+ "maintains"
+ ],
+ "adeeinrt": [
+ "detainer",
+ "retained"
+ ],
+ "aaelmp": [
+ "palame",
+ "palmae",
+ "pamela"
+ ],
+ "adenrsw": [
+ "wanders",
+ "wardens"
+ ],
+ "abelmr": [
+ "ambler",
+ "blamer",
+ "lamber",
+ "marble",
+ "ramble"
+ ],
+ "cehimnoopr": [
+ "microphone",
+ "neomorphic"
+ ],
+ "aginst": [
+ "gainst",
+ "giants",
+ "gisant",
+ "sating"
+ ],
+ "dehs": [
+ "edhs",
+ "shed"
+ ],
+ "emmo": [
+ "memo",
+ "mome"
+ ],
+ "ahm": [
+ "ham",
+ "mah"
+ ],
+ "celnooss": [
+ "consoles",
+ "coolness"
+ ],
+ "bfi": [
+ "fbi",
+ "fib"
+ ],
+ "eelr": [
+ "leer",
+ "lere",
+ "reel"
+ ],
+ "eehrs": [
+ "heres",
+ "herse",
+ "sereh",
+ "sheer",
+ "shree"
+ ],
+ "ginops": [
+ "gipons",
+ "pingos",
+ "posing"
+ ],
+ "bdin": [
+ "bind",
+ "inbd"
+ ],
+ "adnr": [
+ "darn",
+ "nard",
+ "rand"
+ ],
+ "egnrtu": [
+ "gunter",
+ "gurnet",
+ "urgent"
+ ],
+ "chitw": [
+ "tchwi",
+ "wicht",
+ "witch"
+ ],
+ "cehno": [
+ "cohen",
+ "enoch"
+ ],
+ "eis": [
+ "ise",
+ "sei",
+ "sie"
+ ],
+ "eeiprsx": [
+ "expires",
+ "prexies"
+ ],
+ "ellms": [
+ "mells",
+ "smell"
+ ],
+ "acdeinoorst": [
+ "coordinates",
+ "decorations"
+ ],
+ "ceikrst": [
+ "rickets",
+ "sticker",
+ "tickers"
+ ],
+ "eimoprss": [
+ "imposers",
+ "promises",
+ "semipros"
+ ],
+ "bno": [
+ "bon",
+ "nob"
+ ],
+ "deeersv": [
+ "deserve",
+ "severed"
+ ],
+ "ailmot": [
+ "lomita",
+ "tomial"
+ ],
+ "achn": [
+ "chan",
+ "nach"
+ ],
+ "deiorrw": [
+ "rowdier",
+ "wordier",
+ "worried"
+ ],
+ "enstu": [
+ "suent",
+ "tunes",
+ "unset",
+ "usent"
+ ],
+ "cegimnopt": [
+ "coempting",
+ "competing"
+ ],
+ "beht": [
+ "beth",
+ "theb"
+ ],
+ "eln": [
+ "enl",
+ "len"
+ ],
+ "aehprss": [
+ "phasers",
+ "phrases",
+ "seraphs",
+ "shapers",
+ "sherpas"
+ ],
+ "aik": [
+ "aik",
+ "kai"
+ ],
+ "aceehls": [
+ "clashee",
+ "leaches"
+ ],
+ "bginor": [
+ "boring",
+ "orbing",
+ "robing"
+ ],
+ "aceehrs": [
+ "archsee",
+ "reaches",
+ "rechase"
+ ],
+ "acehms": [
+ "sachem",
+ "samech",
+ "schema"
+ ],
+ "afos": [
+ "oafs",
+ "sofa"
+ ],
+ "efiprx": [
+ "perfix",
+ "prefix"
+ ],
+ "acilu": [
+ "aulic",
+ "cauli",
+ "lucia"
+ ],
+ "aelnpst": [
+ "planets",
+ "platens"
+ ],
+ "bdeloru": [
+ "boulder",
+ "doubler"
+ ],
+ "adhlor": [
+ "harold",
+ "holard"
+ ],
+ "ajr": [
+ "jar",
+ "raj"
+ ],
+ "entt": [
+ "nett",
+ "tent"
+ ],
+ "deefiiinst": [
+ "definitise",
+ "identifies"
+ ],
+ "cklos": [
+ "locks",
+ "slock"
+ ],
+ "aelmny": [
+ "laymen",
+ "meanly",
+ "namely"
+ ],
+ "eorsu": [
+ "euros",
+ "roues",
+ "rouse"
+ ],
+ "aaeilr": [
+ "aerial",
+ "aralie",
+ "realia"
+ ],
+ "beelr": [
+ "blere",
+ "rebel"
+ ],
+ "giinors": [
+ "origins",
+ "signior",
+ "signori"
+ ],
+ "dehir": [
+ "dheri",
+ "hider",
+ "hired",
+ "rehid"
+ ],
+ "ablm": [
+ "balm",
+ "blam",
+ "lamb"
+ ],
+ "aahnnt": [
+ "nathan",
+ "thanan"
+ ],
+ "ceeinrstu": [
+ "ceintures",
+ "centuries"
+ ],
+ "dhinu": [
+ "hindu",
+ "hundi",
+ "unhid"
+ ],
+ "aaeehkqrtu": [
+ "earthquake",
+ "heartquake"
+ ],
+ "aeginssss": [
+ "assessing",
+ "gassiness"
+ ],
+ "agilnst": [
+ "anglist",
+ "lasting",
+ "salting",
+ "slating",
+ "staling"
+ ],
+ "cdeinorstu": [
+ "discounter",
+ "introduces",
+ "rediscount",
+ "reductions"
+ ],
+ "cluy": [
+ "cyul",
+ "lucy"
+ ],
+ "ahns": [
+ "hans",
+ "hasn",
+ "nash",
+ "shan"
+ ],
+ "elops": [
+ "elops",
+ "lopes",
+ "olpes",
+ "poles",
+ "slope",
+ "spole"
+ ],
+ "aeeggr": [
+ "agrege",
+ "raggee",
+ "reggae"
+ ],
+ "eopt": [
+ "peto",
+ "poet",
+ "pote",
+ "tope"
+ ],
+ "accinoprsy": [
+ "conspiracy",
+ "snipocracy"
+ ],
+ "aemnrsu": [
+ "manures",
+ "surname"
+ ],
+ "eghlooty": [
+ "ethology",
+ "theology"
+ ],
+ "ailns": [
+ "anils",
+ "nails",
+ "sinal",
+ "slain",
+ "snail"
+ ],
+ "ahstw": [
+ "swath",
+ "thaws",
+ "whats"
+ ],
+ "deirs": [
+ "dries",
+ "resid",
+ "rides",
+ "sider",
+ "sired"
+ ],
+ "ceip": [
+ "epic",
+ "pice"
+ ],
+ "anrstu": [
+ "saturn",
+ "unstar"
+ ],
+ "ntu": [
+ "nut",
+ "tun"
+ ],
+ "aeks": [
+ "kaes",
+ "keas",
+ "sake",
+ "seak"
+ ],
+ "dikss": [
+ "disks",
+ "skids"
+ ],
+ "cdnoo": [
+ "codon",
+ "condo"
+ ],
+ "abeimn": [
+ "benami",
+ "bimane",
+ "embain"
+ ],
+ "anss": [
+ "assn",
+ "sans"
+ ],
+ "bdilsu": [
+ "builds",
+ "sublid"
+ ],
+ "afhst": [
+ "hafts",
+ "shaft"
+ ],
+ "bey": [
+ "bey",
+ "bye"
+ ],
+ "cdeeorrrs": [
+ "recorders",
+ "rerecords"
+ ],
+ "eeills": [
+ "eisell",
+ "leslie",
+ "sellie"
+ ],
+ "aan": [
+ "ana",
+ "naa"
+ ],
+ "ginopsst": [
+ "postings",
+ "postsign",
+ "signpost"
+ ],
+ "adinr": [
+ "darin",
+ "dinar",
+ "drain",
+ "indra",
+ "nadir",
+ "ranid"
+ ],
+ "emnot": [
+ "entom",
+ "monte"
+ ],
+ "efirs": [
+ "fires",
+ "fries",
+ "frise",
+ "reifs",
+ "serif"
+ ],
+ "aaegilr": [
+ "algeria",
+ "argaile",
+ "lairage",
+ "railage",
+ "regalia"
+ ],
+ "bdeelss": [
+ "bedless",
+ "blessed"
+ ],
+ "aooptt": [
+ "pattoo",
+ "potato",
+ "topato"
+ ],
+ "efmorrs": [
+ "formers",
+ "reforms"
+ ],
+ "alot": [
+ "alto",
+ "lota",
+ "tola"
+ ],
+ "cceilrs": [
+ "circles",
+ "clerics"
+ ],
+ "aciilt": [
+ "clitia",
+ "italic"
+ ],
+ "ilm": [
+ "lim",
+ "mil"
+ ],
+ "abcsu": [
+ "cubas",
+ "scuba"
+ ],
+ "egor": [
+ "ergo",
+ "goer",
+ "gore",
+ "ogre",
+ "rego"
+ ],
+ "adhs": [
+ "dahs",
+ "dash",
+ "sadh",
+ "shad"
+ ],
+ "aeipssv": [
+ "passive",
+ "pavises",
+ "pavisse",
+ "spavies"
+ ],
+ "adeluv": [
+ "devaul",
+ "valued"
+ ],
+ "deerv": [
+ "derve",
+ "verde"
+ ],
+ "aceinorss": [
+ "sarcosine",
+ "scenarios"
+ ],
+ "aabmnt": [
+ "bantam",
+ "batman"
+ ],
+ "aeghinrs": [
+ "hearings",
+ "hearsing",
+ "shearing"
+ ],
+ "acelmno": [
+ "aclemon",
+ "cloamen"
+ ],
+ "aelv": [
+ "eval",
+ "lave",
+ "leva",
+ "vale",
+ "veal",
+ "vela"
+ ],
+ "aaehimn": [
+ "anaheim",
+ "anhimae"
+ ],
+ "ddeir": [
+ "dried",
+ "redid"
+ ],
+ "aeegiinnnrtt": [
+ "entertaining",
+ "intenerating"
+ ],
+ "ehlrtu": [
+ "hurtle",
+ "luther",
+ "thurle"
+ ],
+ "ginoppst": [
+ "stopping",
+ "toppings"
+ ],
+ "aelmpr": [
+ "ampler",
+ "emparl",
+ "lamper",
+ "palmer",
+ "relamp"
+ ],
+ "acinopt": [
+ "caption",
+ "paction",
+ "pontiac"
+ ],
+ "cdeinort": [
+ "centroid",
+ "doctrine"
+ ],
+ "aeinrrst": [
+ "restrain",
+ "retrains",
+ "strainer",
+ "terrains",
+ "trainers",
+ "transire"
+ ],
+ "eginsw": [
+ "sewing",
+ "swinge"
+ ],
+ "amno": [
+ "mano",
+ "moan",
+ "mona",
+ "noam",
+ "noma",
+ "oman"
+ ],
+ "cgm": [
+ "cgm",
+ "mcg"
+ ],
+ "eoopprs": [
+ "opposer",
+ "propose"
+ ],
+ "eghilrt": [
+ "lighter",
+ "relight",
+ "rightle"
+ ],
+ "adls": [
+ "lads",
+ "slad"
+ ],
+ "accistt": [
+ "tactics",
+ "tictacs"
+ ],
+ "rssttu": [
+ "struts",
+ "sturts",
+ "trusts"
+ ],
+ "aeinn": [
+ "annie",
+ "ennia",
+ "inane"
+ ],
+ "ehorrst": [
+ "rhetors",
+ "shorter"
+ ],
+ "adeginprs": [
+ "respading",
+ "spreading"
+ ],
+ "acelpr": [
+ "carpel",
+ "parcel",
+ "placer"
+ ],
+ "deefinr": [
+ "definer",
+ "refined"
+ ],
+ "aefrs": [
+ "fares",
+ "farse",
+ "fears",
+ "frase",
+ "safer"
+ ],
+ "enrtu": [
+ "enrut",
+ "tuner",
+ "urent"
+ ],
+ "arsy": [
+ "rays",
+ "ryas"
+ ],
+ "aeikl": [
+ "alike",
+ "kelia",
+ "lakie"
+ ],
+ "altw": [
+ "twal",
+ "walt"
+ ],
+ "acen": [
+ "acne",
+ "cane",
+ "nace"
+ ],
+ "bcdeklo": [
+ "blocked",
+ "deblock"
+ ],
+ "aailmw": [
+ "awalim",
+ "malawi",
+ "mawali"
+ ],
+ "aadn": [
+ "anda",
+ "dana",
+ "nada"
+ ],
+ "ahlo": [
+ "halo",
+ "hola"
+ ],
+ "cirstu": [
+ "citrus",
+ "curtis",
+ "rictus",
+ "rustic"
+ ],
+ "aaelnprt": [
+ "parental",
+ "parlante",
+ "paternal",
+ "prenatal"
+ ],
+ "agsy": [
+ "gays",
+ "sagy"
+ ],
+ "eginprss": [
+ "pressing",
+ "springes"
+ ],
+ "aaaginr": [
+ "agrania",
+ "angaria",
+ "niagara"
+ ],
+ "fin": [
+ "fin",
+ "inf"
+ ],
+ "achmrs": [
+ "charms",
+ "ramsch"
+ ],
+ "aderrt": [
+ "darter",
+ "dartre",
+ "dertra",
+ "redart",
+ "retard",
+ "retrad",
+ "tarred",
+ "trader"
+ ],
+ "aeirss": [
+ "arises",
+ "raises",
+ "serais"
+ ],
+ "egm": [
+ "gem",
+ "meg"
+ ],
+ "ceelort": [
+ "elector",
+ "electro"
+ ],
+ "ceiinorrt": [
+ "cirterion",
+ "criterion",
+ "tricerion"
+ ],
+ "abdeg": [
+ "badge",
+ "begad",
+ "debag"
+ ],
+ "irstw": [
+ "wrist",
+ "writs"
+ ],
+ "aehht": [
+ "heath",
+ "theah"
+ ],
+ "deeeimrs": [
+ "redemise",
+ "remedies"
+ ],
+ "eersttu": [
+ "surette",
+ "trustee"
+ ],
+ "efmoprrs": [
+ "performs",
+ "preforms"
+ ],
+ "aelmr": [
+ "lamer",
+ "realm"
+ ],
+ "aaeilrss": [
+ "assailer",
+ "reassail",
+ "salaries"
+ ],
+ "aimnstu": [
+ "manitus",
+ "tsunami"
+ ],
+ "achlors": [
+ "chorals",
+ "scholar"
+ ],
+ "ceikln": [
+ "nickel",
+ "nickle"
+ ],
+ "acginot": [
+ "coating",
+ "cognati",
+ "cotinga"
+ ],
+ "aelltw": [
+ "wallet",
+ "wellat"
+ ],
+ "acdeelr": [
+ "cedrela",
+ "cleared",
+ "creedal",
+ "declare",
+ "relaced"
+ ],
+ "eiilmss": [
+ "mislies",
+ "missile",
+ "similes"
+ ],
+ "aadeginr": [
+ "drainage",
+ "gardenia"
+ ],
+ "aahikrs": [
+ "kashira",
+ "shikara",
+ "sikhara"
+ ],
+ "cenorrs": [
+ "corners",
+ "scorner"
+ ],
+ "abdeorr": [
+ "arbored",
+ "boarder",
+ "broader",
+ "reboard"
+ ],
+ "aaeeginrtv": [
+ "renavigate",
+ "vegetarian"
+ ],
+ "egoru": [
+ "erugo",
+ "orgue",
+ "rogue",
+ "rouge"
+ ],
+ "aesty": [
+ "yeast",
+ "teasy"
+ ],
+ "acegilnr": [
+ "clearing",
+ "relacing"
+ ],
+ "acdeot": [
+ "coated",
+ "decoat"
+ ],
+ "deinnt": [
+ "dentin",
+ "indent",
+ "intend",
+ "tinned"
+ ],
+ "aeehinpst": [
+ "ephestian",
+ "stephanie"
+ ],
+ "eilosu": [
+ "louies",
+ "louise"
+ ],
+ "enow": [
+ "enow",
+ "owen",
+ "wone"
+ ],
+ "einorstu": [
+ "routines",
+ "rutinose",
+ "snoutier",
+ "tursenoi"
+ ],
+ "ghiintt": [
+ "hitting",
+ "tithing"
+ ],
+ "aceeilnr": [
+ "cerealin",
+ "cinereal",
+ "raceline",
+ "reliance"
+ ],
+ "abhist": [
+ "habits",
+ "tasbih"
+ ],
+ "giiknrst": [
+ "skirting",
+ "striking"
+ ],
+ "ghins": [
+ "nighs",
+ "singh"
+ ],
+ "opsttuu": [
+ "outputs",
+ "putouts"
+ ],
+ "iilnnsu": [
+ "insulin",
+ "inulins"
+ ],
+ "deew": [
+ "wede",
+ "weed"
+ ],
+ "eiiltuz": [
+ "tuilzie",
+ "utilize"
+ ],
+ "aaegln": [
+ "alange",
+ "alnage",
+ "angela",
+ "anlage",
+ "galena",
+ "lagena"
+ ],
+ "aeelrst": [
+ "elaters",
+ "laertes",
+ "realest",
+ "relates",
+ "reslate",
+ "resteal",
+ "seletar",
+ "stealer",
+ "teasler"
+ ],
+ "cdei": [
+ "cedi",
+ "dice",
+ "iced"
+ ],
+ "dmos": [
+ "doms",
+ "mods"
+ ],
+ "adeginors": [
+ "grandiose",
+ "organdies",
+ "organised",
+ "sargonide"
+ ],
+ "orsst": [
+ "sorts",
+ "ssort",
+ "sstor"
+ ],
+ "eiinorssv": [
+ "ivoriness",
+ "revisions"
+ ],
+ "deeorrst": [
+ "resorted",
+ "restored"
+ ],
+ "amorr": [
+ "armor",
+ "maror",
+ "morra"
+ ],
+ "deirrs": [
+ "derris",
+ "driers",
+ "riders"
+ ],
+ "denosz": [
+ "dozens",
+ "zendos"
+ ],
+ "aeirsv": [
+ "aivers",
+ "sairve",
+ "varies"
+ ],
+ "adgrsu": [
+ "gradus",
+ "guards"
+ ],
+ "acegilnpr": [
+ "parceling",
+ "replacing"
+ ],
+ "adeehrstw": [
+ "drawsheet",
+ "watershed"
+ ],
+ "ccilnosu": [
+ "councils",
+ "succinol"
+ ],
+ "ailrv": [
+ "rival",
+ "viral"
+ ],
+ "eipps": [
+ "pepsi",
+ "pipes"
+ ],
+ "adeln": [
+ "alden",
+ "eland",
+ "laden",
+ "lande",
+ "lenad",
+ "naled"
+ ],
+ "aelorrt": [
+ "realtor",
+ "relator"
+ ],
+ "aeiimnostt": [
+ "estimation",
+ "testimonia"
+ ],
+ "abnr": [
+ "barn",
+ "bran"
+ ],
+ "ghinpsu": [
+ "gunship",
+ "pushing"
+ ],
+ "acdeiiprt": [
+ "dipicrate",
+ "patricide",
+ "pediatric"
+ ],
+ "bco": [
+ "boc",
+ "cob"
+ ],
+ "emprs": [
+ "perms",
+ "sperm"
+ ],
+ "abdl": [
+ "bald",
+ "blad"
+ ],
+ "acprs": [
+ "carps",
+ "craps",
+ "scarp",
+ "scrap"
+ ],
+ "forst": [
+ "forst",
+ "forts",
+ "frost"
+ ],
+ "elno": [
+ "elon",
+ "enol",
+ "leno",
+ "leon",
+ "lone",
+ "noel"
+ ],
+ "achty": [
+ "cathy",
+ "cyath",
+ "yacht"
+ ],
+ "acrty": [
+ "carty",
+ "tracy"
+ ],
+ "mpt": [
+ "pmt",
+ "tpm"
+ ],
+ "abcehr": [
+ "barche",
+ "brache",
+ "breach",
+ "chaber"
+ ],
+ "aehlw": [
+ "halwe",
+ "whale",
+ "wheal"
+ ],
+ "deil": [
+ "deil",
+ "deli",
+ "diel",
+ "eild",
+ "idle",
+ "lide",
+ "lied"
+ ],
+ "agmnstu": [
+ "mustang",
+ "stagnum"
+ ],
+ "aoprst": [
+ "asport",
+ "pastor",
+ "portas",
+ "sproat"
+ ],
+ "dmu": [
+ "dum",
+ "mud"
+ ],
+ "ahkrs": [
+ "harks",
+ "shark"
+ ],
+ "abdeeilrs": [
+ "desirable",
+ "redisable"
+ ],
+ "abellt": [
+ "ballet",
+ "batell"
+ ],
+ "egiilnors": [
+ "ligroines",
+ "religions"
+ ],
+ "ehins": [
+ "eshin",
+ "hsien",
+ "shine"
+ ],
+ "befirs": [
+ "briefs",
+ "febris",
+ "fibers",
+ "fibres"
+ ],
+ "ceov": [
+ "cove",
+ "voce"
+ ],
+ "aceinnoorsstv": [
+ "conservations",
+ "conversations"
+ ],
+ "adiors": [
+ "aroids",
+ "radios"
+ ],
+ "adiinv": [
+ "avidin",
+ "vidian"
+ ],
+ "aapst": [
+ "apast",
+ "pasta",
+ "patas",
+ "tapas"
+ ],
+ "emrsu": [
+ "mures",
+ "muser",
+ "remus",
+ "serum"
+ ],
+ "eimnrtu": [
+ "minuter",
+ "runtime",
+ "unmiter",
+ "unmitre"
+ ],
+ "acflo": [
+ "falco",
+ "focal"
+ ],
+ "adinstt": [
+ "dantist",
+ "distant"
+ ],
+ "ciln": [
+ "clin",
+ "incl"
+ ],
+ "aal": [
+ "aal",
+ "ala"
+ ],
+ "aabms": [
+ "ambas",
+ "samba"
+ ],
+ "elmopy": [
+ "employ",
+ "pomely"
+ ],
+ "aacgilm": [
+ "camalig",
+ "magical"
+ ],
+ "aceilmr": [
+ "calmier",
+ "claimer",
+ "milacre",
+ "miracle",
+ "reclaim"
+ ],
+ "cdeeenrt": [
+ "centered",
+ "decenter",
+ "decentre",
+ "recedent"
+ ],
+ "aelryy": [
+ "yarely",
+ "yearly",
+ "layery"
+ ],
+ "aors": [
+ "asor",
+ "oars",
+ "oras",
+ "osar",
+ "rosa",
+ "soar",
+ "sora"
+ ],
+ "ahhs": [
+ "hahs",
+ "hash",
+ "sahh",
+ "shah"
+ ],
+ "gmp": [
+ "gpm",
+ "mpg"
+ ],
+ "aefhrst": [
+ "fathers",
+ "hafters",
+ "shafter"
+ ],
+ "deiln": [
+ "eldin",
+ "lined"
+ ],
+ "dow": [
+ "dow",
+ "owd",
+ "wod"
+ ],
+ "diu": [
+ "dui",
+ "iud",
+ "udi"
+ ],
+ "eny": [
+ "eyn",
+ "yen",
+ "nye"
+ ],
+ "iprsst": [
+ "spirts",
+ "sprits",
+ "stirps",
+ "strips"
+ ],
+ "aegnrrs": [
+ "garners",
+ "rangers"
+ ],
+ "morw": [
+ "morw",
+ "worm"
+ ],
+ "cdeeirst": [
+ "creedist",
+ "desertic",
+ "discreet",
+ "discrete"
+ ],
+ "dil": [
+ "dil",
+ "lid"
+ ],
+ "eeloprsty": [
+ "polyester",
+ "proselyte"
+ ],
+ "adef": [
+ "deaf",
+ "fade"
+ ],
+ "aehipprs": [
+ "papisher",
+ "sapphire"
+ ],
+ "aeikns": [
+ "kinase",
+ "sekani",
+ "skenai"
+ ],
+ "ikrsst": [
+ "skirts",
+ "stirks"
+ ],
+ "amst": [
+ "mast",
+ "mats",
+ "stam",
+ "tams"
+ ],
+ "abdeell": [
+ "beladle",
+ "labeled"
+ ],
+ "abeirs": [
+ "braies",
+ "braise",
+ "rabies",
+ "rebias",
+ "serbia"
+ ],
+ "ffgiinr": [
+ "griffin",
+ "riffing"
+ ],
+ "aagnuy": [
+ "guanay",
+ "guyana"
+ ],
+ "eipss": [
+ "sipes",
+ "spies",
+ "spise"
+ ],
+ "eimm": [
+ "emim",
+ "mime"
+ ],
+ "acceennortt": [
+ "concentrate",
+ "concertante"
+ ],
+ "eglnost": [
+ "longest",
+ "songlet"
+ ],
+ "inost": [
+ "nitos",
+ "sinto",
+ "stion"
+ ],
+ "ceeennsst": [
+ "senescent",
+ "sentences"
+ ],
+ "eflry": [
+ "ferly",
+ "flyer",
+ "refly"
+ ],
+ "aeps": [
+ "apes",
+ "apse",
+ "pase",
+ "peas",
+ "pesa",
+ "spae"
+ ],
+ "adegos": [
+ "dagoes",
+ "dosage",
+ "seadog"
+ ],
+ "ceeeirrsv": [
+ "receivers",
+ "reservice"
+ ],
+ "acemnoor": [
+ "cameroon",
+ "coenamor"
+ ],
+ "deeeln": [
+ "lendee",
+ "needle"
+ ],
+ "abhst": [
+ "bahts",
+ "baths"
+ ],
+ "aainnrv": [
+ "navarin",
+ "nirvana"
+ ],
+ "ademnss": [
+ "desmans",
+ "dsnames",
+ "madness"
+ ],
+ "acems": [
+ "acmes",
+ "cames",
+ "maces"
+ ],
+ "ahy": [
+ "hay",
+ "yah"
+ ],
+ "aaccdir": [
+ "arcadic",
+ "cardiac"
+ ],
+ "aailnostv": [
+ "lavations",
+ "salvation"
+ ],
+ "deorv": [
+ "dover",
+ "drove",
+ "roved",
+ "vedro",
+ "voder"
+ ],
+ "aadinr": [
+ "adrian",
+ "andira",
+ "andria",
+ "radian",
+ "randia"
+ ],
+ "aeelnrrs": [
+ "learners",
+ "relearns"
+ ],
+ "ceeeilstv": [
+ "cleveites",
+ "electives",
+ "selective"
+ ],
+ "adeiilorst": [
+ "editorials",
+ "idolatries",
+ "idolatrise",
+ "radiolites"
+ ],
+ "eeekrss": [
+ "reseeks",
+ "seekers"
+ ],
+ "ais": [
+ "ais",
+ "sai",
+ "sia"
+ ],
+ "abeelmorv": [
+ "overblame",
+ "removable"
+ ],
+ "ceimnru": [
+ "micerun",
+ "numeric",
+ "uncrime"
+ ],
+ "agiknst": [
+ "gitksan",
+ "skating",
+ "staking",
+ "takings",
+ "tasking"
+ ],
+ "belst": [
+ "belts",
+ "blest",
+ "blets"
+ ],
+ "abeerst": [
+ "beaters",
+ "berates",
+ "bestare",
+ "rebates"
+ ],
+ "eeoprrrst": [
+ "preresort",
+ "reporters"
+ ],
+ "bekru": [
+ "bruke",
+ "burke"
+ ],
+ "ceeinssty": [
+ "cysteines",
+ "necessity"
+ ],
+ "ekly": [
+ "yelk",
+ "kyle"
+ ],
+ "cersuv": [
+ "cervus",
+ "curves"
+ ],
+ "aaclr": [
+ "clara",
+ "craal"
+ ],
+ "aelrstv": [
+ "travels",
+ "varlets",
+ "vestral"
+ ],
+ "aeirvw": [
+ "waiver",
+ "wavier"
+ ],
+ "aelp": [
+ "leap",
+ "lepa",
+ "pale",
+ "peal",
+ "plea"
+ ],
+ "deghilt": [
+ "delight",
+ "lighted"
+ ],
+ "ceeimnoos": [
+ "economies",
+ "economise",
+ "monoecies"
+ ],
+ "aabceilrt": [
+ "bacterial",
+ "calibrate"
+ ],
+ "agps": [
+ "gaps",
+ "gasp",
+ "spag"
+ ],
+ "dnoors": [
+ "donors",
+ "rondos"
+ ],
+ "aat": [
+ "ata",
+ "taa"
+ ],
+ "aceehst": [
+ "escheat",
+ "teaches"
+ ],
+ "iln": [
+ "lin",
+ "nil"
+ ],
+ "aegnrrst": [
+ "granters",
+ "regrants",
+ "stranger"
+ ],
+ "adegrty": [
+ "gyrated",
+ "tragedy"
+ ],
+ "derry": [
+ "derry",
+ "dryer",
+ "redry",
+ "ryder"
+ ],
+ "abilnrtu": [
+ "tribunal",
+ "turbinal",
+ "untribal"
+ ],
+ "delru": [
+ "duler",
+ "lured",
+ "ruled",
+ "urled"
+ ],
+ "anot": [
+ "nato",
+ "nota",
+ "tano"
+ ],
+ "einnopss": [
+ "pensions",
+ "snipnose"
+ ],
+ "aeprrsy": [
+ "prayers",
+ "respray",
+ "sprayer"
+ ],
+ "eehnorw": [
+ "nowhere",
+ "whereon"
+ ],
+ "cop": [
+ "cop",
+ "cpo"
+ ],
+ "aegl": [
+ "egal",
+ "gael",
+ "gale",
+ "geal"
+ ],
+ "aellty": [
+ "lately",
+ "lealty"
+ ],
+ "acrsy": [
+ "ascry",
+ "sacry",
+ "scary",
+ "scray"
+ ],
+ "aemrsstt": [
+ "mattress",
+ "smartest",
+ "smatters"
+ ],
+ "beinru": [
+ "burnie",
+ "eburin",
+ "rubine"
+ ],
+ "eeiiklsw": [
+ "likewise",
+ "wiselike"
+ ],
+ "anst": [
+ "ants",
+ "nast",
+ "sant",
+ "stan",
+ "tans"
+ ],
+ "dilo": [
+ "dilo",
+ "diol",
+ "doli",
+ "idol",
+ "lido",
+ "olid"
+ ],
+ "deimnr": [
+ "minder",
+ "remind"
+ ],
+ "acghimnr": [
+ "charming",
+ "marching"
+ ],
+ "cdeeeprst": [
+ "respected",
+ "sceptered",
+ "spectered"
+ ],
+ "assty": [
+ "sayst",
+ "stays"
+ ],
+ "aaffir": [
+ "affair",
+ "raffia"
+ ],
+ "aehrsw": [
+ "hawser",
+ "rewash",
+ "washer"
+ ],
+ "ceirrstt": [
+ "critters",
+ "restrict",
+ "stricter"
+ ],
+ "eginprrs": [
+ "respring",
+ "springer"
+ ],
+ "eimns": [
+ "miens",
+ "mines"
+ ],
+ "bdenoru": [
+ "beround",
+ "bounder",
+ "rebound",
+ "unbored",
+ "unorbed",
+ "unrobed"
+ ],
+ "emnort": [
+ "mentor",
+ "merton",
+ "metron",
+ "montre",
+ "termon",
+ "tormen"
+ ],
+ "eefmoprrr": [
+ "performer",
+ "prereform",
+ "reperform"
+ ],
+ "deiltt": [
+ "tilted",
+ "titled"
+ ],
+ "eehprs": [
+ "herpes",
+ "hesper",
+ "sphere"
+ ],
+ "aiorst": [
+ "aorist",
+ "aristo",
+ "ratios",
+ "satori"
+ ],
+ "addelr": [
+ "ladder",
+ "larded",
+ "raddle"
+ ],
+ "aenorsst": [
+ "assentor",
+ "essorant",
+ "senators",
+ "starnose",
+ "treasons"
+ ],
+ "koortuw": [
+ "outwork",
+ "workout"
+ ],
+ "aelns": [
+ "ansel",
+ "elans",
+ "lanes",
+ "leans",
+ "senal",
+ "slane"
+ ],
+ "aginstt": [
+ "stating",
+ "tasting"
+ ],
+ "aceilnor": [
+ "acrolein",
+ "arecolin",
+ "caroline",
+ "colinear",
+ "cornelia",
+ "creolian",
+ "lonicera"
+ ],
+ "elu": [
+ "leu",
+ "lue",
+ "ule"
+ ],
+ "agiln": [
+ "algin",
+ "align",
+ "langi",
+ "liang",
+ "ligan",
+ "linga"
+ ],
+ "adeimnnot": [
+ "dentinoma",
+ "nominated"
+ ],
+ "aceellort": [
+ "electoral",
+ "recollate"
+ ],
+ "eehl": [
+ "heel",
+ "hele"
+ ],
+ "alloy": [
+ "alloy",
+ "loyal"
+ ],
+ "cdnoos": [
+ "codons",
+ "condos"
+ ],
+ "lopst": [
+ "plots",
+ "topsl"
+ ],
+ "dehilops": [
+ "depolish",
+ "polished"
+ ],
+ "alstu": [
+ "altus",
+ "latus",
+ "sault",
+ "talus",
+ "tulsa"
+ ],
+ "adrsw": [
+ "draws",
+ "sward",
+ "wards"
+ ],
+ "lou": [
+ "lou",
+ "luo"
+ ],
+ "cdeeeorrv": [
+ "overcreed",
+ "recovered"
+ ],
+ "aefrrs": [
+ "farers",
+ "fraser"
+ ],
+ "egrsu": [
+ "grues",
+ "guser",
+ "surge",
+ "urges"
+ ],
+ "aamrtu": [
+ "taruma",
+ "trauma"
+ ],
+ "addegimnn": [
+ "demanding",
+ "maddening"
+ ],
+ "lossu": [
+ "solus",
+ "souls"
+ ],
+ "aknps": [
+ "knaps",
+ "spank"
+ ],
+ "egoorv": [
+ "groove",
+ "overgo"
+ ],
+ "aeeilnortv": [
+ "relevation",
+ "revelation"
+ ],
+ "adeegilnot": [
+ "degelation",
+ "delegation"
+ ],
+ "eirsw": [
+ "swire",
+ "weirs",
+ "wires",
+ "wiser",
+ "wries"
+ ],
+ "eelpss": [
+ "sleeps",
+ "speels"
+ ],
+ "abekl": [
+ "blake",
+ "bleak",
+ "kabel"
+ ],
+ "eginr": [
+ "grein",
+ "inger",
+ "nigre",
+ "regin",
+ "reign",
+ "renig",
+ "ringe"
+ ],
+ "abcno": [
+ "bacon",
+ "banco"
+ ],
+ "aeehrt": [
+ "aether",
+ "heater",
+ "hereat",
+ "reheat"
+ ],
+ "ccirsu": [
+ "circus",
+ "crucis"
+ ],
+ "demooprt": [
+ "promoted",
+ "toperdom"
+ ],
+ "aem": [
+ "ame",
+ "eam",
+ "mae",
+ "mea"
+ ],
+ "elm": [
+ "elm",
+ "mel"
+ ],
+ "einps": [
+ "insep",
+ "peins",
+ "penis",
+ "pines",
+ "snipe",
+ "spine"
+ ],
+ "orttu": [
+ "tourt",
+ "trout",
+ "tutor"
+ ],
+ "aeilmoprrty": [
+ "polarimetry",
+ "premorality",
+ "temporarily"
+ ],
+ "ehillrrt": [
+ "rethrill",
+ "thriller"
+ ],
+ "aimnrstt": [
+ "tantrism",
+ "transmit"
+ ],
+ "adeglr": [
+ "argled",
+ "gerald",
+ "glared",
+ "regald"
+ ],
+ "deeprss": [
+ "depress",
+ "pressed"
+ ],
+ "eghnru": [
+ "hunger",
+ "rehung"
+ ],
+ "pssu": [
+ "puss",
+ "sups"
+ ],
+ "aeinnrtt": [
+ "antirent",
+ "internat",
+ "intranet"
+ ],
+ "celoss": [
+ "closes",
+ "socles"
+ ],
+ "eqs": [
+ "esq",
+ "seq"
+ ],
+ "eeinprsstt": [
+ "persistent",
+ "pinsetters",
+ "presentist",
+ "prettiness"
+ ],
+ "aeimmrssu": [
+ "summaries",
+ "summarise"
+ ],
+ "glow": [
+ "glow",
+ "gowl"
+ ],
+ "amw": [
+ "awm",
+ "maw",
+ "mwa"
+ ],
+ "deiox": [
+ "doxie",
+ "oxide"
+ ],
+ "akos": [
+ "asok",
+ "koas",
+ "oaks",
+ "okas",
+ "soak",
+ "soka"
+ ],
+ "eikr": [
+ "erik",
+ "keir",
+ "kier",
+ "reki"
+ ],
+ "ceelnorsu": [
+ "enclosure",
+ "recounsel"
+ ],
+ "aeknrw": [
+ "newark",
+ "wanker"
+ ],
+ "mnor": [
+ "morn",
+ "norm"
+ ],
+ "elrttu": [
+ "ruttle",
+ "turtle",
+ "tutler"
+ ],
+ "abinooprst": [
+ "absorption",
+ "probations",
+ "saprobiont"
+ ],
+ "abdly": [
+ "badly",
+ "baldy",
+ "blady"
+ ],
+ "alloop": [
+ "apollo",
+ "palolo"
+ ],
+ "anw": [
+ "awn",
+ "naw",
+ "wan"
+ ],
+ "aeinprs": [
+ "paniers",
+ "persian",
+ "prasine",
+ "rapines",
+ "saprine",
+ "spirane"
+ ],
+ "eeegnr": [
+ "neeger",
+ "reenge",
+ "renege"
+ ],
+ "boorst": [
+ "brosot",
+ "robots"
+ ],
+ "adej": [
+ "deja",
+ "jade"
+ ],
+ "coops": [
+ "coops",
+ "scoop"
+ ],
+ "aeginnr": [
+ "aginner",
+ "earning",
+ "engrain",
+ "geranin",
+ "grannie",
+ "nearing"
+ ],
+ "deenst": [
+ "dentes",
+ "nested",
+ "sedent",
+ "tensed"
+ ],
+ "emorsv": [
+ "movers",
+ "vomers"
+ ],
+ "abelrv": [
+ "barvel",
+ "blaver",
+ "verbal"
+ ],
+ "eelnprsty": [
+ "presently",
+ "serpently"
+ ],
+ "aess": [
+ "asse",
+ "seas"
+ ],
+ "eilst": [
+ "islet",
+ "istle",
+ "liest",
+ "lites",
+ "slite",
+ "stile",
+ "tiles"
+ ],
+ "eops": [
+ "epos",
+ "opes",
+ "peso",
+ "pose",
+ "sope"
+ ],
+ "ikloott": [
+ "kittool",
+ "toolkit"
+ ],
+ "agot": [
+ "goat",
+ "toag",
+ "toga"
+ ],
+ "deenrr": [
+ "derner",
+ "render"
+ ],
+ "ens": [
+ "ens",
+ "sen"
+ ],
+ "efgor": [
+ "forge",
+ "gofer"
+ ],
+ "ceiimmnoorsss": [
+ "commissioners",
+ "recommissions"
+ ],
+ "aberv": [
+ "brave",
+ "breva"
+ ],
+ "afluw": [
+ "awful",
+ "fulwa"
+ ],
+ "aaeilnpr": [
+ "airplane",
+ "perianal"
+ ],
+ "eenrst": [
+ "enters",
+ "ernest",
+ "nester",
+ "rentes",
+ "resent",
+ "streen",
+ "tenser",
+ "ternes"
+ ],
+ "dop": [
+ "dop",
+ "pod"
+ ],
+ "aaegsv": [
+ "agaves",
+ "savage"
+ ],
+ "blot": [
+ "blot",
+ "bolt"
+ ],
+ "ghinortw": [
+ "ingrowth",
+ "throwing",
+ "worthing"
+ ],
+ "agnow": [
+ "gowan",
+ "wagon",
+ "wonga"
+ ],
+ "adt": [
+ "dat",
+ "tad"
+ ],
+ "egru": [
+ "grue",
+ "urge"
+ ],
+ "aeeegnrst": [
+ "generates",
+ "teenagers"
+ ],
+ "krtu": [
+ "kurt",
+ "turk"
+ ],
+ "eeprs": [
+ "peers",
+ "peres",
+ "perse",
+ "prees",
+ "prese",
+ "speer",
+ "spere",
+ "spree"
+ ],
+ "orsu": [
+ "ours",
+ "rous",
+ "sour"
+ ],
+ "eefhrrs": [
+ "fresher",
+ "refresh"
+ ],
+ "belstu": [
+ "bluest",
+ "bluets",
+ "bustle",
+ "butles",
+ "sublet",
+ "subtle"
+ ],
+ "eiprsst": [
+ "esprits",
+ "persist",
+ "priests",
+ "spriest",
+ "sprites",
+ "stirpes",
+ "stripes"
+ ],
+ "ceop": [
+ "cope",
+ "opec"
+ ],
+ "acdelr": [
+ "cardel",
+ "cradle",
+ "credal",
+ "reclad"
+ ],
+ "ikkr": [
+ "kirk",
+ "rikk"
+ ],
+ "floru": [
+ "flour",
+ "fluor"
+ ],
+ "adeeglnry": [
+ "enragedly",
+ "legendary"
+ ],
+ "bloo": [
+ "bolo",
+ "bool",
+ "lobo",
+ "loob",
+ "obol"
+ ],
+ "ahmpstyy": [
+ "sympathy",
+ "symphyta"
+ ],
+ "chior": [
+ "chiro",
+ "choir",
+ "ichor"
+ ],
+ "ceepstx": [
+ "excepts",
+ "expects"
+ ],
+ "cho": [
+ "cho",
+ "hoc",
+ "och"
+ ],
+ "itw": [
+ "twi",
+ "wit"
+ ],
+ "brstu": [
+ "burst",
+ "strub"
+ ],
+ "din": [
+ "din",
+ "ind",
+ "nid"
+ ],
+ "coprsu": [
+ "corpus",
+ "croups"
+ ],
+ "adehss": [
+ "dashes",
+ "sadhes",
+ "sashed",
+ "shades"
+ ],
+ "ejst": [
+ "jest",
+ "jets"
+ ],
+ "cdeiins": [
+ "incised",
+ "indices"
+ ],
+ "ilnt": [
+ "intl",
+ "lint"
+ ],
+ "adisy": [
+ "daisy",
+ "sayid"
+ ],
+ "corrsu": [
+ "cruors",
+ "cursor"
+ ],
+ "aeeilrst": [
+ "ateliers",
+ "earliest",
+ "leariest",
+ "realties",
+ "tresaiel"
+ ],
+ "addenot": [
+ "donated",
+ "nodated"
+ ],
+ "deffstu": [
+ "destuff",
+ "stuffed"
+ ],
+ "ceinsst": [
+ "incests",
+ "insects",
+ "scenist"
+ ],
+ "aeilmnrst": [
+ "mislearnt",
+ "terminals",
+ "trailsmen",
+ "tramlines"
+ ],
+ "cderu": [
+ "crude",
+ "cured"
+ ],
+ "elmrty": [
+ "myrtle",
+ "termly"
+ ],
+ "bdeor": [
+ "boder",
+ "bored",
+ "orbed",
+ "robed"
+ ],
+ "acelnpu": [
+ "cleanup",
+ "unplace"
+ ],
+ "iknt": [
+ "knit",
+ "tink"
+ ],
+ "gmu": [
+ "gum",
+ "mug"
+ ],
+ "behort": [
+ "bother",
+ "brothe"
+ ],
+ "abhntu": [
+ "bhutan",
+ "thuban"
+ ],
+ "agimnt": [
+ "mating",
+ "taming"
+ ],
+ "addeehr": [
+ "adhered",
+ "redhead"
+ ],
+ "aeirrsv": [
+ "arrives",
+ "riserva",
+ "variers"
+ ],
+ "aahll": [
+ "allah",
+ "halal"
+ ],
+ "anpruw": [
+ "unwarp",
+ "unwrap"
+ ],
+ "dehop": [
+ "depoh",
+ "ephod",
+ "hoped"
+ ],
+ "eikp": [
+ "kepi",
+ "kipe",
+ "pike"
+ ],
+ "ghou": [
+ "hugo",
+ "ough"
+ ],
+ "aegnrw": [
+ "gnawer",
+ "wagner",
+ "wanger"
+ ],
+ "aegnrr": [
+ "garner",
+ "ranger"
+ ],
+ "ahmrs": [
+ "harms",
+ "marsh",
+ "shram"
+ ],
+ "aehtt": [
+ "hatte",
+ "theat",
+ "theta"
+ ]
+}
diff --git a/bot/resources/fun/latex_template.txt b/bot/resources/fun/latex_template.txt
new file mode 100644
index 00000000..a20cc279
--- /dev/null
+++ b/bot/resources/fun/latex_template.txt
@@ -0,0 +1,5 @@
+\documentclass{article}
+\begin{document}
+ \pagenumbering{gobble}
+ $text
+\end{document}
diff --git a/bot/resources/fun/madlibs_templates.json b/bot/resources/fun/madlibs_templates.json
new file mode 100644
index 00000000..0023d48f
--- /dev/null
+++ b/bot/resources/fun/madlibs_templates.json
@@ -0,0 +1,135 @@
+[
+ {
+ "title": "How To Cross a Piranha-Infested River",
+ "blanks": ["foreign country", "adverb", "adjective", "animal",
+ "verb ending in 'ing'", "verb", "verb ending in 'ing'",
+ "adverb", "adjective", "a place", "type of liquid", "part of the body", "verb"],
+ "value": ["If you are traveling in ", " and find yourself having to cross a piranha-filled river, here's how to do it ",
+ ": \n* Piranhas are more ", " during the day, so cross the river at night.\n* Avoid areas with netted ",
+ " traps--piranhas may be ", " there looking to ", " them!\n* When "," the river, swim ",
+ ". You don't want to wake them up and make them ", "!\n* Whatever you do, if you have an open wound, try to find another way to get back to the ",
+ ". Piranhas are attracted to fresh ", " and will most likely take a bite out of your ", " if you ", " in the water!"]
+ },
+ {
+ "title": "Three Little Pigs",
+ "blanks": ["adjective", "verb", "verb", "verb", "plural noun", "verb", "verb", "past tense verb", "plural noun", "adjective", "verb",
+ "plural noun", "noun", "verb", "past tense verb", "noun", "noun", "noun", "past tense verb", "adjective", "past tense verb",
+ "past tense verb", "noun", "past tense verb"],
+ "value": ["Once up a time, there were three ", " pigs. One day, their mother said, \"You are all grown up and must ", " on your own.\" So they left to ",
+ " their houses. The first little pig wanted only to ", " all day and quickly built his house out of ", ". The second little pig wanted to ",
+ " and ", " all day so he ", " his house with ", ". The third ", " pig knew the wolf lived nearby and worked hard to ", " his house out of ",
+ ". One day, the wolf knocked on the first pig's ", ". \"Let me in or I'll ", " your house down!\" The pig didn't, so the wolf ", " down the ",
+ ". The wolf knocked on the second pig's ", ". \"Let me in or I'll blow your ", " down!\" The pig didn't, so the wolf ",
+ " down the house. Then the wolf knocked on the third ", " pig's door. \"Let me in or I'll blow your house down!\" The little pig didn't so the wolf ",
+ " and ", ". He could not blow the house down. All the pigs went to live in the ", " house and they all ", " happily ever after."]
+ },
+ {
+ "title": "Talk Like a Pirate",
+ "blanks": ["noun", "adjective", "verb", "adverb", "noun", "adjective", "plural noun", "plural noun", "plural noun", "part of the body", "noun",
+ "noun", "noun", "noun", "part of the body"],
+ "value": ["Ye can always pretend to be a bloodthirsty ", ", threatening everyone by waving yer ", " sword in the air, but until ye learn to ",
+ " like a pirate, ye'll never be ", " accepted as an authentic ", ". So here's what ye do: Cleverly work into yer daily conversations ",
+ " pirate phrases such as \"Ahoy there, ", "Avast, ye ", ",\" and \"Shiver me ", ".\" Remember to drop all yer gs when ye say such words as sailin', spittin', and fightin'. This will give ye a/an ",
+ " start to being recognized as a swashbucklin' ", ". Once ye have the lingo down pat, it helps to wear a three-cornered ", " on yer head, stash a/an ",
+ " in yer pants, and keep a/an ", " perched atop yer ", ". Aye, now ye be a real pirate!"]
+ },
+ {
+ "title": "How to Date the Coolest Guy/Girl in School",
+ "blanks": ["plural noun", "adverb", "verb", "article of clothing", "body part", "adjective", "noun", "plural noun", "another body part", "plural noun",
+ "another body part", "noun", "noun", "verb ending in 'ing'", "adjective", "adjective", "verb"],
+ "value": ["It's simple. Turn the ", ". Make him/her want ", " to date you. Make sure you're always dressed to ", ". Each and every day, wear a/an ",
+ " that you know shows off your ", " to ", " advantage and make your ", " look like a million ", ". Even if the two of you make meaningful ",
+ " contact, don't admit it. No hugs or ", ". Just shake his/her ", " firmly. And remember, when he/she asks you out, even though a chill may run down your ",
+ " and you can't stop your ", " from ", ", just play it ", ". Take a long pause before answering in a very ", " voice. \"I'll have to ",
+ " it over.\""]
+ },
+ {
+ "title": "The Fun Park",
+ "blanks": ["adjective", "plural noun", "noun", "adverb", "number", "past tense verb", "adjective ending in -est", "past tense verb", "adverb", "adjective"],
+ "value": ["Today, my fabulous camp group went to a(an) ", " amusement park. It was a fun park with lots of cool ",
+ " and enjoyable play structures. When we got there, my kind counselor shouted loudly, \"Everybody off the ",
+ ".\" My counselor handed out yellow tickets, and we scurried in. I was so excited! I couldn't figure out what exciting thing to do first. I saw a scary roller coaster I really liked so, I ",
+ " ran over to get in the long line that had about ", " people in it. when I finally got on the roller coaster I was ",
+ ". In fact, I was so nervous my two knees were knocking together. This was the ", " ride I had ever been on! In about two minutes I heard the crank and grinding of the gears. Thats when the ride began! When I got to the bottom, I was a little ",
+ " but I was proud of myself. The rest of the day went ", ". It was a ", " day at the fun park."]
+ },
+ {
+ "title": "A Spooky Campfire Story",
+ "blanks": ["adjective", "adjective", "number", "adjective", "animal", "noun", "animal", "name", "verb", "adjective", "adjective"],
+ "value": ["Every summer, I get totally amped and ", " to go camping in the deep, ", " forests. It's good to get away from it all - but not too far, like getting lost! Last year, my friend and I went hiking and got lost for ",
+ " hour(s). We started off on a(n) ", " adventure, but we kept losing the trail. Night began to fall, and when we heard the howls of a ",
+ ", we began to panic. It was getting darker and our flashlights were running on ", ". I'm sure glad my pet ", ", ", ", was with us. He is one gifted creature, because he was able to guide us back by ",
+ " the ", " s'mores by the campfire. This year, before setting off on an ", " journey, I'll be sure to have working flashlights - and of course, my gifted pet!"]
+ },
+ {
+ "title": "Weird News",
+ "blanks": ["noun", "place", "verb ending in ing", "noun", "name", "verb", "noun", "verb", "noun", "part of body", "type of liquid", "place", " past tense verb ", "foreign country", "verb", "noun", "past tense verb", "adjective", "verb", "noun", "plural noun"],
+ "value": ["A ", " in a ", " was arrested this morning after he was caught ", " in front of ", ". ", " had a history of ", ", but no one - not even his ", "- ever imagined he'd ", " with a ", " stuck in his ", ". After drinking a ", ", cops followed him to a ",
+ " where he reportedly ", " in the fry machine. Later, a woman from ", " was charged with a similar crime. But rather than ", " with a ", ", she ", " with a ", " dog. Either way, we imagine that after witnessing him ", " with a ", " there are probably a whole lot of ",
+ " that are going to need some therapy!"]
+ },
+ {
+ "title": "All About Vampires",
+ "blanks": ["adjective", "adjective", "body part", "verb ending in -ing", "verb", "verb", "verb", "noun", "verb", "verb", "body part", "verb (ending with -s)", "verb (ending with -s)", "verb", "noun", "body part", "adjective"],
+ "value": ["Vampires are ", "! They have ", " ", " for ", " blood. The sun can ", " vampires, so they only ", " at night and ", " during the day. Vampires also don't like ", " so ", " it or ", " it around your ",
+ " to keep them away. If a vampire ", "s a person and ", "s their blood, they become a vampire, too. The only way to ", " a vampire is with a ", " through the ", ", but ",
+ " luck getting close enough to one!"]
+ },
+ {
+ "title": "Our Cafeteria",
+ "blanks": ["adjective", "verb", "adjective", "noun", "verb", "adjective", "noun", "adjective", "adjective", "noun", "noun"],
+ "value": ["Our school cafeteria has really ", " food. Just thinking about it makes my stomach ", ". The spaghetti is ", " and tastes like ", ". One day, I swear one of my meatballs started to ",
+ "! The turkey tacos are totally ", " and look kind of like old ", ". My friend Dana actually likes the meatloaf, even though it's ", " and ", ". I call it \"Mystery Meatloaf\" and think it's really made out of ",
+ ". My dad said he'd make my lunches, but the first day, he made me a sandwich out of ", " and peanut butter! I think I'd rather take my chances with the cafeteria!"]
+ },
+ {
+ "title": "Trip to the Park",
+ "blanks": ["adjective", "adjective", "noun", "adjective", "adjective", "verb", "verb", "verb", "adjective", "verb"],
+ "value": ["Yesterday, my friend and I went to the park. On our way to the ", " park, we saw big ", " balloons tied to a ", ". Once we got to the ", " park, the sky turned ",
+ ". It started to ", " and ", ". My friend and I ", " all the way home. Tomorrow we will try to go to the ", " park again and hopefully it doesn't ", "!"]
+ },
+ {
+ "title": "A Scary Halloween Story",
+ "blanks": ["adjective", "name", "adjective", "noun", "verb", "animal", "adjective", "name", "adjective", "noun", "noun"],
+ "value": ["They say my school is haunted; my ", " friend ", " says they saw a ", " ", " floating at the end of the hall near the cafeteria. Some say if you ",
+ " down that hallway at night, you'll hear a ", " growling deeply. My ", " friend ", " saw a ", " ", " slithering under the tables once. I hope I never see any ",
+ " crawling; eating lunch there is scary enough!"]
+ },
+ {
+ "title": "Zombie Picnic",
+ "blanks": ["verb", "verb", "body part", "body part", "body part", "noun", "adjective", "type of liquid", "body part", "verb", "adjective", "body part", "body part"],
+ "value": ["If zombies had a picnic, what would they ", " to eat? Everybody knows zombies love to ", " ", ", but did you know they also enjoy ", " and even ",
+ "? The best ", " for a zombie picnic is when the moon is ", ". At least one zombie will bring ", " to drink, and it's not a picnic without ",
+ " with extra flesh on top. After eating, zombies will ", " ", " games like kick the ", " and ", " toss. What fun!"]
+ },
+ {
+ "title": "North Pole",
+ "blanks": ["plural noun", "adjective", "plural noun", "verb", "verb", "number", "noun", "adjective", "plural noun", "plural noun", "animal", "verb", "verb", "plural noun"],
+ "value": ["Santa, Mrs. Claus, and the ", " live at the North pole. The weather is always ", " there, but the ", " ", " toys for Santa to ",
+ " to children on Christmas, so holiday cheer lasts year-round there. There's no land at the North Pole; instead there is a ", "-inch thick sheet of ",
+ " there, ", " enough to hold Santa's Village! The ", " help load Santa's sleigh with ", ", and Santa's ", " ", " his sleigh on Christmas Eve to ",
+ " ", " to children around the entire world."]
+ },
+ {
+ "title": "Snowstorm!",
+ "blanks": ["plural noun", "adjective", "noun", "noun", "adjective", "adjective", "adjective", "noun", "noun", "adjective"],
+ "value": ["Weather plays an important part in our ", " everyday. What is weather, you ask? According to ", " scientists, who are known as meteorologists, weather is what the ",
+ " is like at any given time of the ", ". It doesn't matter if the air is ", " or ", ", it's all weather. When vapors in ", " clouds condense, we have ",
+ " and snow. A lot of ", " means a ", " snowstorm!"]
+ },
+ {
+ "title": "Learning About History",
+ "blanks": ["adjective", "noun", "nouns", "adjective", "nouns", "nouns", "animals", "nouns", "nouns", "number", "number", "nouns", "adjective", "nouns"],
+ "value": ["History is ", " because we learn about ", " and ", " that happened long ago. I can't believe people used to dress in ", " clothing and kids played with ",
+ " and ", " instead of video games. Also, before cars were invented, people actually rode ", "! People read ", " instead of computers and tablets, and sent messages via ",
+ " that took ", " days to arrive. I wonder how kids will view my life in ", " year(s); maybe they will ride flying cars to school and play with ", " and ", " ", "!"]
+ },
+ {
+ "title": "Star Wars",
+ "blanks": ["adjective", "noun", "adjective", "noun; place", "adjective", "adjective", "adjective", "adjective", "plural noun", "adjective", "plural noun", "plural noun", "adjective",
+ "noun", "verb (ending with -s)", "adjective", "verb", "plural noun; type of job", "adjective", "verb", "adjective"],
+ "value": ["Star Wars is a ", " ", " of ", " versus evil in a ", " far far away. There are ", " battles between ", " ships in ", " space and ", " duels with ", " called ",
+ " sabers. ", " called \"droids\" are helpers and ", " to the heroes. A ", " power called The ", " ", " people to do ", " things, like ", " ", " use The Force for the ",
+ " side and the Sith ", " it for the ", " side."]
+ }
+]
diff --git a/bot/resources/fun/trivia_quiz.json b/bot/resources/fun/trivia_quiz.json
index 0b3e6802..99aa5f42 100644
--- a/bot/resources/fun/trivia_quiz.json
+++ b/bot/resources/fun/trivia_quiz.json
@@ -440,7 +440,7 @@
{
"id": 229,
"question": "What is this triangle called?",
- "img_url": "https://cdn.askpython.com/wp-content/uploads/2020/07/Pascals-triangle.png",
+ "img_url": "https://wikimedia.org/api/rest_v1/media/math/render/png/23050fcb53d6083d9e42043bebf2863fa9746043",
"answer": ["Pascal's triangle", "Pascal"]
},
{
diff --git a/bot/resources/holidays/halloween/bat-clipart.png b/bot/resources/holidays/halloween/bat-clipart.png
index 7df26ba9..fc2f77b0 100644
--- a/bot/resources/holidays/halloween/bat-clipart.png
+++ b/bot/resources/holidays/halloween/bat-clipart.png
Binary files differ
diff --git a/bot/resources/utilities/py_topics.yaml b/bot/resources/utilities/py_topics.yaml
index a3fb2ccc..92fc0b73 100644
--- a/bot/resources/utilities/py_topics.yaml
+++ b/bot/resources/utilities/py_topics.yaml
@@ -33,15 +33,33 @@
- How often do you program in Python?
- How would you learn a new library if needed to do so?
- Have you ever worked with a microcontroller or anything physical with Python before?
- - How good would you say you are at Python so far? Beginner, intermediate, or advanced?
- Have you ever tried making your own programming language?
- Has a recently discovered Python module changed your general use of Python?
+ - What is your motivation for programming?
+ - What's your favorite Python related book?
+ - What's your favorite use of recursion in Python?
+ - If you could change one thing in Python, what would it be?
+ - What third-party library do you wish was in the Python standard library?
+ - Which package do you use the most and why?
+ - Which Python feature do you love the most?
+ - Do you have any plans for future projects?
+ - What modules/libraries do you want to see more projects using?
+ - What's the most ambitious thing you've done with Python so far?
+
+# programming-pedagogy
+934931964509691966:
+ - What is the best way to teach/learn OOP?
+ - What benefits are there to teaching programming to students who aren't training to become developers?
+ - What are some basic concepts that we need to know before teaching programming to others?
+ - What are the most common difficulties/misconceptions students encounter while learning to program?
+ - What makes a project a good learning experience for beginners?
+ - What can make difficult concepts more fun for students to learn?
# algos-and-data-structs
650401909852864553:
-
-# async
+# async-and-concurrency
630504881542791169:
- Are there any frameworks you wish were async?
- How have coroutines changed the way you write Python?
@@ -55,12 +73,13 @@
342318764227821568:
- Where do you get your best data?
- What is your preferred database and for what use?
+ - What is the least safe use of databases you've seen?
-# data-science
+# data-science-and-ai
366673247892275221:
-
-# discord.py
+# discord-bots
343944376055103488:
- What unique features does your bot contain, if any?
- What commands/features are you proud of making?
@@ -79,6 +98,8 @@
- What's a common part of programming we can make harder?
- What are the pros and cons of messing with __magic__()?
- What's your favorite Python hack?
+ - What's the weirdest language feature that Python doesn't have, and how can we change that?
+ - What is the most esoteric code you've written?
# game-development
660625198390837248:
@@ -111,6 +132,10 @@
- How often do you use GitHub Actions and workflows to automate your repositories?
- What's your favorite app on GitHub?
+# type-hinting
+891788761371906108:
+ -
+
# unit-testing
463035728335732738:
-
@@ -121,6 +146,7 @@
- What's your most used Bash command?
- How often do you update your Unix machine?
- How often do you upgrade on production?
+ - What is your least favorite thing about interoperability amongst *NIX operating systems and/or platforms?
# user-interfaces
338993628049571840:
@@ -129,6 +155,7 @@
- Do you perfer Command Line Interfaces (CLI) or Graphic User Interfaces (GUI)?
- What's your favorite CLI (Command Line Interface) or TUI (Terminal Line Interface)?
- What's your best GUI project?
+ - What the best-looking app you've used?
# web-development
366673702533988363:
@@ -137,3 +164,4 @@
- What is your favorite API library?
- What do you use for your frontend?
- What does your stack look like?
+ - What's the best-looking website you've visited?
diff --git a/bot/resources/utilities/ryanzec_colours.json b/bot/resources/utilities/ryanzec_colours.json
new file mode 100644
index 00000000..552d5a3f
--- /dev/null
+++ b/bot/resources/utilities/ryanzec_colours.json
@@ -0,0 +1,1569 @@
+{
+ "_": "Source: https://github.com/ryanzec/name-that-color/blob/0bb5ec7f37e4f6e7f2c164f39f7f08cca7c8e621/lib/ntc.js#L116-L1681\n\nCopyright (c) 2014 Ryan Zec\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
+ "Abbey": "4C4F56",
+ "Acadia": "1B1404",
+ "Acapulco": "7CB0A1",
+ "Aero Blue": "C9FFE5",
+ "Affair": "714693",
+ "Akaroa": "D4C4A8",
+ "Alabaster": "FAFAFA",
+ "Albescent White": "F5E9D3",
+ "Algae Green": "93DFB8",
+ "Alice Blue": "F0F8FF",
+ "Alizarin Crimson": "E32636",
+ "Allports": "0076A3",
+ "Almond": "EED9C4",
+ "Almond Frost": "907B71",
+ "Alpine": "AF8F2C",
+ "Alto": "DBDBDB",
+ "Aluminium": "A9ACB6",
+ "Amaranth": "E52B50",
+ "Amazon": "3B7A57",
+ "Amber": "FFBF00",
+ "Americano": "87756E",
+ "Amethyst": "9966CC",
+ "Amethyst Smoke": "A397B4",
+ "Amour": "F9EAF3",
+ "Amulet": "7B9F80",
+ "Anakiwa": "9DE5FF",
+ "Antique Brass": "C88A65",
+ "Antique Bronze": "704A07",
+ "Anzac": "E0B646",
+ "Apache": "DFBE6F",
+ "Apple": "4FA83D",
+ "Apple Blossom": "AF4D43",
+ "Apple Green": "E2F3EC",
+ "Apricot": "EB9373",
+ "Apricot Peach": "FBCEB1",
+ "Apricot White": "FFFEEC",
+ "Aqua Deep": "014B43",
+ "Aqua Forest": "5FA777",
+ "Aqua Haze": "EDF5F5",
+ "Aqua Island": "A1DAD7",
+ "Aqua Spring": "EAF9F5",
+ "Aqua Squeeze": "E8F5F2",
+ "Aquamarine": "7FFFD4",
+ "Aquamarine Blue": "71D9E2",
+ "Arapawa": "110C6C",
+ "Armadillo": "433E37",
+ "Arrowtown": "948771",
+ "Ash": "C6C3B5",
+ "Asparagus": "7BA05B",
+ "Asphalt": "130A06",
+ "Astra": "FAEAB9",
+ "Astral": "327DA0",
+ "Astronaut": "283A77",
+ "Astronaut Blue": "013E62",
+ "Athens Gray": "EEF0F3",
+ "Aths Special": "ECEBCE",
+ "Atlantis": "97CD2D",
+ "Atoll": "0A6F75",
+ "Atomic Tangerine": "FF9966",
+ "Au Chico": "97605D",
+ "Aubergine": "3B0910",
+ "Australian Mint": "F5FFBE",
+ "Avocado": "888D65",
+ "Axolotl": "4E6649",
+ "Azalea": "F7C8DA",
+ "Aztec": "0D1C19",
+ "Azure": "315BA1",
+ "Azure Radiance": "007FFF",
+ "Baby Blue": "E0FFFF",
+ "Bahama Blue": "026395",
+ "Bahia": "A5CB0C",
+ "Baja White": "FFF8D1",
+ "Bali Hai": "859FAF",
+ "Baltic Sea": "2A2630",
+ "Bamboo": "DA6304",
+ "Banana Mania": "FBE7B2",
+ "Bandicoot": "858470",
+ "Barberry": "DED717",
+ "Barley Corn": "A68B5B",
+ "Barley White": "FFF4CE",
+ "Barossa": "44012D",
+ "Bastille": "292130",
+ "Battleship Gray": "828F72",
+ "Bay Leaf": "7DA98D",
+ "Bay of Many": "273A81",
+ "Bazaar": "98777B",
+ "Bean ": "3D0C02",
+ "Beauty Bush": "EEC1BE",
+ "Beaver": "926F5B",
+ "Beeswax": "FEF2C7",
+ "Beige": "F5F5DC",
+ "Bermuda": "7DD8C6",
+ "Bermuda Gray": "6B8BA2",
+ "Beryl Green": "DEE5C0",
+ "Bianca": "FCFBF3",
+ "Big Stone": "162A40",
+ "Bilbao": "327C14",
+ "Biloba Flower": "B2A1EA",
+ "Birch": "373021",
+ "Bird Flower": "D4CD16",
+ "Biscay": "1B3162",
+ "Bismark": "497183",
+ "Bison Hide": "C1B7A4",
+ "Bistre": "3D2B1F",
+ "Bitter": "868974",
+ "Bitter Lemon": "CAE00D",
+ "Bittersweet": "FE6F5E",
+ "Bizarre": "EEDEDA",
+ "Black": "000000",
+ "Black Bean": "081910",
+ "Black Forest": "0B1304",
+ "Black Haze": "F6F7F7",
+ "Black Marlin": "3E2C1C",
+ "Black Olive": "242E16",
+ "Black Pearl": "041322",
+ "Black Rock": "0D0332",
+ "Black Rose": "67032D",
+ "Black Russian": "0A001C",
+ "Black Squeeze": "F2FAFA",
+ "Black White": "FFFEF6",
+ "Blackberry": "4D0135",
+ "Blackcurrant": "32293A",
+ "Blaze Orange": "FF6600",
+ "Bleach White": "FEF3D8",
+ "Bleached Cedar": "2C2133",
+ "Blizzard Blue": "A3E3ED",
+ "Blossom": "DCB4BC",
+ "Blue": "0000FF",
+ "Blue Bayoux": "496679",
+ "Blue Bell": "9999CC",
+ "Blue Chalk": "F1E9FF",
+ "Blue Charcoal": "010D1A",
+ "Blue Chill": "0C8990",
+ "Blue Diamond": "380474",
+ "Blue Dianne": "204852",
+ "Blue Gem": "2C0E8C",
+ "Blue Haze": "BFBED8",
+ "Blue Lagoon": "017987",
+ "Blue Marguerite": "7666C6",
+ "Blue Ribbon": "0066FF",
+ "Blue Romance": "D2F6DE",
+ "Blue Smoke": "748881",
+ "Blue Stone": "016162",
+ "Blue Violet": "6456B7",
+ "Blue Whale": "042E4C",
+ "Blue Zodiac": "13264D",
+ "Blumine": "18587A",
+ "Blush": "B44668",
+ "Blush Pink": "FF6FFF",
+ "Bombay": "AFB1B8",
+ "Bon Jour": "E5E0E1",
+ "Bondi Blue": "0095B6",
+ "Bone": "E4D1C0",
+ "Bordeaux": "5C0120",
+ "Bossanova": "4E2A5A",
+ "Boston Blue": "3B91B4",
+ "Botticelli": "C7DDE5",
+ "Bottle Green": "093624",
+ "Boulder": "7A7A7A",
+ "Bouquet": "AE809E",
+ "Bourbon": "BA6F1E",
+ "Bracken": "4A2A04",
+ "Brandy": "DEC196",
+ "Brandy Punch": "CD8429",
+ "Brandy Rose": "BB8983",
+ "Breaker Bay": "5DA19F",
+ "Brick Red": "C62D42",
+ "Bridal Heath": "FFFAF4",
+ "Bridesmaid": "FEF0EC",
+ "Bright Gray": "3C4151",
+ "Bright Green": "66FF00",
+ "Bright Red": "B10000",
+ "Bright Sun": "FED33C",
+ "Bright Turquoise": "08E8DE",
+ "Brilliant Rose": "F653A6",
+ "Brink Pink": "FB607F",
+ "Bronco": "ABA196",
+ "Bronze": "3F2109",
+ "Bronze Olive": "4E420C",
+ "Bronzetone": "4D400F",
+ "Broom": "FFEC13",
+ "Brown": "964B00",
+ "Brown Bramble": "592804",
+ "Brown Derby": "492615",
+ "Brown Pod": "401801",
+ "Brown Rust": "AF593E",
+ "Brown Tumbleweed": "37290E",
+ "Bubbles": "E7FEFF",
+ "Buccaneer": "622F30",
+ "Bud": "A8AE9C",
+ "Buddha Gold": "C1A004",
+ "Buff": "F0DC82",
+ "Bulgarian Rose": "480607",
+ "Bull Shot": "864D1E",
+ "Bunker": "0D1117",
+ "Bunting": "151F4C",
+ "Burgundy": "900020",
+ "Burnham": "002E20",
+ "Burning Orange": "FF7034",
+ "Burning Sand": "D99376",
+ "Burnt Maroon": "420303",
+ "Burnt Orange": "CC5500",
+ "Burnt Sienna": "E97451",
+ "Burnt Umber": "8A3324",
+ "Bush": "0D2E1C",
+ "Buttercup": "F3AD16",
+ "Buttered Rum": "A1750D",
+ "Butterfly Bush": "624E9A",
+ "Buttermilk": "FFF1B5",
+ "Buttery White": "FFFCEA",
+ "Cab Sav": "4D0A18",
+ "Cabaret": "D94972",
+ "Cabbage Pont": "3F4C3A",
+ "Cactus": "587156",
+ "Cadet Blue": "A9B2C3",
+ "Cadillac": "B04C6A",
+ "Cafe Royale": "6F440C",
+ "Calico": "E0C095",
+ "California": "FE9D04",
+ "Calypso": "31728D",
+ "Camarone": "00581A",
+ "Camelot": "893456",
+ "Cameo": "D9B99B",
+ "Camouflage": "3C3910",
+ "Camouflage Green": "78866B",
+ "Can Can": "D591A4",
+ "Canary": "F3FB62",
+ "Candlelight": "FCD917",
+ "Candy Corn": "FBEC5D",
+ "Cannon Black": "251706",
+ "Cannon Pink": "894367",
+ "Cape Cod": "3C4443",
+ "Cape Honey": "FEE5AC",
+ "Cape Palliser": "A26645",
+ "Caper": "DCEDB4",
+ "Caramel": "FFDDAF",
+ "Cararra": "EEEEE8",
+ "Cardin Green": "01361C",
+ "Cardinal": "C41E3A",
+ "Cardinal Pink": "8C055E",
+ "Careys Pink": "D29EAA",
+ "Caribbean Green": "00CC99",
+ "Carissma": "EA88A8",
+ "Carla": "F3FFD8",
+ "Carmine": "960018",
+ "Carnaby Tan": "5C2E01",
+ "Carnation": "F95A61",
+ "Carnation Pink": "FFA6C9",
+ "Carousel Pink": "F9E0ED",
+ "Carrot Orange": "ED9121",
+ "Casablanca": "F8B853",
+ "Casal": "2F6168",
+ "Cascade": "8BA9A5",
+ "Cashmere": "E6BEA5",
+ "Casper": "ADBED1",
+ "Castro": "52001F",
+ "Catalina Blue": "062A78",
+ "Catskill White": "EEF6F7",
+ "Cavern Pink": "E3BEBE",
+ "Cedar": "3E1C14",
+ "Cedar Wood Finish": "711A00",
+ "Celadon": "ACE1AF",
+ "Celery": "B8C25D",
+ "Celeste": "D1D2CA",
+ "Cello": "1E385B",
+ "Celtic": "163222",
+ "Cement": "8D7662",
+ "Ceramic": "FCFFF9",
+ "Cerise": "DA3287",
+ "Cerise Red": "DE3163",
+ "Cerulean": "02A4D3",
+ "Cerulean Blue": "2A52BE",
+ "Chablis": "FFF4F3",
+ "Chalet Green": "516E3D",
+ "Chalky": "EED794",
+ "Chambray": "354E8C",
+ "Chamois": "EDDCB1",
+ "Champagne": "FAECCC",
+ "Chantilly": "F8C3DF",
+ "Charade": "292937",
+ "Chardon": "FFF3F1",
+ "Chardonnay": "FFCD8C",
+ "Charlotte": "BAEEF9",
+ "Charm": "D47494",
+ "Chartreuse": "7FFF00",
+ "Chartreuse Yellow": "DFFF00",
+ "Chateau Green": "40A860",
+ "Chatelle": "BDB3C7",
+ "Chathams Blue": "175579",
+ "Chelsea Cucumber": "83AA5D",
+ "Chelsea Gem": "9E5302",
+ "Chenin": "DFCD6F",
+ "Cherokee": "FCDA98",
+ "Cherry Pie": "2A0359",
+ "Cherrywood": "651A14",
+ "Cherub": "F8D9E9",
+ "Chestnut": "B94E48",
+ "Chestnut Rose": "CD5C5C",
+ "Chetwode Blue": "8581D9",
+ "Chicago": "5D5C58",
+ "Chiffon": "F1FFC8",
+ "Chilean Fire": "F77703",
+ "Chilean Heath": "FFFDE6",
+ "China Ivory": "FCFFE7",
+ "Chino": "CEC7A7",
+ "Chinook": "A8E3BD",
+ "Chocolate": "370202",
+ "Christalle": "33036B",
+ "Christi": "67A712",
+ "Christine": "E7730A",
+ "Chrome White": "E8F1D4",
+ "Cinder": "0E0E18",
+ "Cinderella": "FDE1DC",
+ "Cinnabar": "E34234",
+ "Cinnamon": "7B3F00",
+ "Cioccolato": "55280C",
+ "Citrine White": "FAF7D6",
+ "Citron": "9EA91F",
+ "Citrus": "A1C50A",
+ "Clairvoyant": "480656",
+ "Clam Shell": "D4B6AF",
+ "Claret": "7F1734",
+ "Classic Rose": "FBCCE7",
+ "Clay Ash": "BDC8B3",
+ "Clay Creek": "8A8360",
+ "Clear Day": "E9FFFD",
+ "Clementine": "E96E00",
+ "Clinker": "371D09",
+ "Cloud": "C7C4BF",
+ "Cloud Burst": "202E54",
+ "Cloudy": "ACA59F",
+ "Clover": "384910",
+ "Cobalt": "0047AB",
+ "Cocoa Bean": "481C1C",
+ "Cocoa Brown": "301F1E",
+ "Coconut Cream": "F8F7DC",
+ "Cod Gray": "0B0B0B",
+ "Coffee": "706555",
+ "Coffee Bean": "2A140E",
+ "Cognac": "9F381D",
+ "Cola": "3F2500",
+ "Cold Purple": "ABA0D9",
+ "Cold Turkey": "CEBABA",
+ "Colonial White": "FFEDBC",
+ "Comet": "5C5D75",
+ "Como": "517C66",
+ "Conch": "C9D9D2",
+ "Concord": "7C7B7A",
+ "Concrete": "F2F2F2",
+ "Confetti": "E9D75A",
+ "Congo Brown": "593737",
+ "Congress Blue": "02478E",
+ "Conifer": "ACDD4D",
+ "Contessa": "C6726B",
+ "Copper": "B87333",
+ "Copper Canyon": "7E3A15",
+ "Copper Rose": "996666",
+ "Copper Rust": "944747",
+ "Copperfield": "DA8A67",
+ "Coral": "FF7F50",
+ "Coral Red": "FF4040",
+ "Coral Reef": "C7BCA2",
+ "Coral Tree": "A86B6B",
+ "Corduroy": "606E68",
+ "Coriander": "C4D0B0",
+ "Cork": "40291D",
+ "Corn": "E7BF05",
+ "Corn Field": "F8FACD",
+ "Corn Harvest": "8B6B0B",
+ "Cornflower": "93CCEA",
+ "Cornflower Blue": "6495ED",
+ "Cornflower Lilac": "FFB0AC",
+ "Corvette": "FAD3A2",
+ "Cosmic": "76395D",
+ "Cosmos": "FFD8D9",
+ "Costa Del Sol": "615D30",
+ "Cotton Candy": "FFB7D5",
+ "Cotton Seed": "C2BDB6",
+ "County Green": "01371A",
+ "Cowboy": "4D282D",
+ "Crail": "B95140",
+ "Cranberry": "DB5079",
+ "Crater Brown": "462425",
+ "Cream": "FFFDD0",
+ "Cream Brulee": "FFE5A0",
+ "Cream Can": "F5C85C",
+ "Creole": "1E0F04",
+ "Crete": "737829",
+ "Crimson": "DC143C",
+ "Crocodile": "736D58",
+ "Crown of Thorns": "771F1F",
+ "Crowshead": "1C1208",
+ "Cruise": "B5ECDF",
+ "Crusoe": "004816",
+ "Crusta": "FD7B33",
+ "Cumin": "924321",
+ "Cumulus": "FDFFD5",
+ "Cupid": "FBBEDA",
+ "Curious Blue": "2596D1",
+ "Cutty Sark": "507672",
+ "Cyan / Aqua": "00FFFF",
+ "Cyprus": "003E40",
+ "Daintree": "012731",
+ "Dairy Cream": "F9E4BC",
+ "Daisy Bush": "4F2398",
+ "Dallas": "6E4B26",
+ "Dandelion": "FED85D",
+ "Danube": "6093D1",
+ "Dark Blue": "0000C8",
+ "Dark Burgundy": "770F05",
+ "Dark Ebony": "3C2005",
+ "Dark Fern": "0A480D",
+ "Dark Tan": "661010",
+ "Dawn": "A6A29A",
+ "Dawn Pink": "F3E9E5",
+ "De York": "7AC488",
+ "Deco": "D2DA97",
+ "Deep Blue": "220878",
+ "Deep Blush": "E47698",
+ "Deep Bronze": "4A3004",
+ "Deep Cerulean": "007BA7",
+ "Deep Cove": "051040",
+ "Deep Fir": "002900",
+ "Deep Forest Green": "182D09",
+ "Deep Koamaru": "1B127B",
+ "Deep Oak": "412010",
+ "Deep Sapphire": "082567",
+ "Deep Sea": "01826B",
+ "Deep Sea Green": "095859",
+ "Deep Teal": "003532",
+ "Del Rio": "B09A95",
+ "Dell": "396413",
+ "Delta": "A4A49D",
+ "Deluge": "7563A8",
+ "Denim": "1560BD",
+ "Derby": "FFEED8",
+ "Desert": "AE6020",
+ "Desert Sand": "EDC9AF",
+ "Desert Storm": "F8F8F7",
+ "Dew": "EAFFFE",
+ "Di Serria": "DB995E",
+ "Diesel": "130000",
+ "Dingley": "5D7747",
+ "Disco": "871550",
+ "Dixie": "E29418",
+ "Dodger Blue": "1E90FF",
+ "Dolly": "F9FF8B",
+ "Dolphin": "646077",
+ "Domino": "8E775E",
+ "Don Juan": "5D4C51",
+ "Donkey Brown": "A69279",
+ "Dorado": "6B5755",
+ "Double Colonial White": "EEE3AD",
+ "Double Pearl Lusta": "FCF4D0",
+ "Double Spanish White": "E6D7B9",
+ "Dove Gray": "6D6C6C",
+ "Downriver": "092256",
+ "Downy": "6FD0C5",
+ "Driftwood": "AF8751",
+ "Drover": "FDF7AD",
+ "Dull Lavender": "A899E6",
+ "Dune": "383533",
+ "Dust Storm": "E5CCC9",
+ "Dusty Gray": "A8989B",
+ "Eagle": "B6BAA4",
+ "Earls Green": "C9B93B",
+ "Early Dawn": "FFF9E6",
+ "East Bay": "414C7D",
+ "East Side": "AC91CE",
+ "Eastern Blue": "1E9AB0",
+ "Ebb": "E9E3E3",
+ "Ebony": "0C0B1D",
+ "Ebony Clay": "26283B",
+ "Eclipse": "311C17",
+ "Ecru White": "F5F3E5",
+ "Ecstasy": "FA7814",
+ "Eden": "105852",
+ "Edgewater": "C8E3D7",
+ "Edward": "A2AEAB",
+ "Egg Sour": "FFF4DD",
+ "Egg White": "FFEFC1",
+ "Eggplant": "614051",
+ "El Paso": "1E1708",
+ "El Salva": "8F3E33",
+ "Electric Lime": "CCFF00",
+ "Electric Violet": "8B00FF",
+ "Elephant": "123447",
+ "Elf Green": "088370",
+ "Elm": "1C7C7D",
+ "Emerald": "50C878",
+ "Eminence": "6C3082",
+ "Emperor": "514649",
+ "Empress": "817377",
+ "Endeavour": "0056A7",
+ "Energy Yellow": "F8DD5C",
+ "English Holly": "022D15",
+ "English Walnut": "3E2B23",
+ "Envy": "8BA690",
+ "Equator": "E1BC64",
+ "Espresso": "612718",
+ "Eternity": "211A0E",
+ "Eucalyptus": "278A5B",
+ "Eunry": "CFA39D",
+ "Evening Sea": "024E46",
+ "Everglade": "1C402E",
+ "Faded Jade": "427977",
+ "Fair Pink": "FFEFEC",
+ "Falcon": "7F626D",
+ "Fall Green": "ECEBBD",
+ "Falu Red": "801818",
+ "Fantasy": "FAF3F0",
+ "Fedora": "796A78",
+ "Feijoa": "9FDD8C",
+ "Fern": "63B76C",
+ "Fern Frond": "657220",
+ "Fern Green": "4F7942",
+ "Ferra": "704F50",
+ "Festival": "FBE96C",
+ "Feta": "F0FCEA",
+ "Fiery Orange": "B35213",
+ "Finch": "626649",
+ "Finlandia": "556D56",
+ "Finn": "692D54",
+ "Fiord": "405169",
+ "Fire": "AA4203",
+ "Fire Bush": "E89928",
+ "Firefly": "0E2A30",
+ "Flame Pea": "DA5B38",
+ "Flamenco": "FF7D07",
+ "Flamingo": "F2552A",
+ "Flax": "EEDC82",
+ "Flax Smoke": "7B8265",
+ "Flesh": "FFCBA4",
+ "Flint": "6F6A61",
+ "Flirt": "A2006D",
+ "Flush Mahogany": "CA3435",
+ "Flush Orange": "FF7F00",
+ "Foam": "D8FCFA",
+ "Fog": "D7D0FF",
+ "Foggy Gray": "CBCAB6",
+ "Forest Green": "228B22",
+ "Forget Me Not": "FFF1EE",
+ "Fountain Blue": "56B4BE",
+ "Frangipani": "FFDEB3",
+ "French Gray": "BDBDC6",
+ "French Lilac": "ECC7EE",
+ "French Pass": "BDEDFD",
+ "French Rose": "F64A8A",
+ "Fresh Eggplant": "990066",
+ "Friar Gray": "807E79",
+ "Fringy Flower": "B1E2C1",
+ "Froly": "F57584",
+ "Frost": "EDF5DD",
+ "Frosted Mint": "DBFFF8",
+ "Frostee": "E4F6E7",
+ "Fruit Salad": "4F9D5D",
+ "Fuchsia Blue": "7A58C1",
+ "Fuchsia Pink": "C154C1",
+ "Fuego": "BEDE0D",
+ "Fuel Yellow": "ECA927",
+ "Fun Blue": "1959A8",
+ "Fun Green": "016D39",
+ "Fuscous Gray": "54534D",
+ "Fuzzy Wuzzy Brown": "C45655",
+ "Gable Green": "163531",
+ "Gallery": "EFEFEF",
+ "Galliano": "DCB20C",
+ "Gamboge": "E49B0F",
+ "Geebung": "D18F1B",
+ "Genoa": "15736B",
+ "Geraldine": "FB8989",
+ "Geyser": "D4DFE2",
+ "Ghost": "C7C9D5",
+ "Gigas": "523C94",
+ "Gimblet": "B8B56A",
+ "Gin": "E8F2EB",
+ "Gin Fizz": "FFF9E2",
+ "Givry": "F8E4BF",
+ "Glacier": "80B3C4",
+ "Glade Green": "61845F",
+ "Go Ben": "726D4E",
+ "Goblin": "3D7D52",
+ "Gold": "FFD700",
+ "Gold Drop": "F18200",
+ "Gold Sand": "E6BE8A",
+ "Gold Tips": "DEBA13",
+ "Golden Bell": "E28913",
+ "Golden Dream": "F0D52D",
+ "Golden Fizz": "F5FB3D",
+ "Golden Glow": "FDE295",
+ "Golden Grass": "DAA520",
+ "Golden Sand": "F0DB7D",
+ "Golden Tainoi": "FFCC5C",
+ "Goldenrod": "FCD667",
+ "Gondola": "261414",
+ "Gordons Green": "0B1107",
+ "Gorse": "FFF14F",
+ "Gossamer": "069B81",
+ "Gossip": "D2F8B0",
+ "Gothic": "6D92A1",
+ "Governor Bay": "2F3CB3",
+ "Grain Brown": "E4D5B7",
+ "Grandis": "FFD38C",
+ "Granite Green": "8D8974",
+ "Granny Apple": "D5F6E3",
+ "Granny Smith": "84A0A0",
+ "Granny Smith Apple": "9DE093",
+ "Grape": "381A51",
+ "Graphite": "251607",
+ "Gravel": "4A444B",
+ "Gray": "808080",
+ "Gray Asparagus": "465945",
+ "Gray Chateau": "A2AAB3",
+ "Gray Nickel": "C3C3BD",
+ "Gray Nurse": "E7ECE6",
+ "Gray Olive": "A9A491",
+ "Gray Suit": "C1BECD",
+ "Green": "00FF00",
+ "Green Haze": "01A368",
+ "Green House": "24500F",
+ "Green Kelp": "25311C",
+ "Green Leaf": "436A0D",
+ "Green Mist": "CBD3B0",
+ "Green Pea": "1D6142",
+ "Green Smoke": "A4AF6E",
+ "Green Spring": "B8C1B1",
+ "Green Vogue": "032B52",
+ "Green Waterloo": "101405",
+ "Green White": "E8EBE0",
+ "Green Yellow": "ADFF2F",
+ "Grenadier": "D54600",
+ "Guardsman Red": "BA0101",
+ "Gulf Blue": "051657",
+ "Gulf Stream": "80B3AE",
+ "Gull Gray": "9DACB7",
+ "Gum Leaf": "B6D3BF",
+ "Gumbo": "7CA1A6",
+ "Gun Powder": "414257",
+ "Gunsmoke": "828685",
+ "Gurkha": "9A9577",
+ "Hacienda": "98811B",
+ "Hairy Heath": "6B2A14",
+ "Haiti": "1B1035",
+ "Half Baked": "85C4CC",
+ "Half Colonial White": "FDF6D3",
+ "Half Dutch White": "FEF7DE",
+ "Half Spanish White": "FEF4DB",
+ "Half and Half": "FFFEE1",
+ "Hampton": "E5D8AF",
+ "Harlequin": "3FFF00",
+ "Harp": "E6F2EA",
+ "Harvest Gold": "E0B974",
+ "Havelock Blue": "5590D9",
+ "Hawaiian Tan": "9D5616",
+ "Hawkes Blue": "D4E2FC",
+ "Heath": "541012",
+ "Heather": "B7C3D0",
+ "Heathered Gray": "B6B095",
+ "Heavy Metal": "2B3228",
+ "Heliotrope": "DF73FF",
+ "Hemlock": "5E5D3B",
+ "Hemp": "907874",
+ "Hibiscus": "B6316C",
+ "Highland": "6F8E63",
+ "Hillary": "ACA586",
+ "Himalaya": "6A5D1B",
+ "Hint of Green": "E6FFE9",
+ "Hint of Red": "FBF9F9",
+ "Hint of Yellow": "FAFDE4",
+ "Hippie Blue": "589AAF",
+ "Hippie Green": "53824B",
+ "Hippie Pink": "AE4560",
+ "Hit Gray": "A1ADB5",
+ "Hit Pink": "FFAB81",
+ "Hokey Pokey": "C8A528",
+ "Hoki": "65869F",
+ "Holly": "011D13",
+ "Hollywood Cerise": "F400A1",
+ "Honey Flower": "4F1C70",
+ "Honeysuckle": "EDFC84",
+ "Hopbush": "D06DA1",
+ "Horizon": "5A87A0",
+ "Horses Neck": "604913",
+ "Hot Cinnamon": "D2691E",
+ "Hot Pink": "FF69B4",
+ "Hot Toddy": "B38007",
+ "Humming Bird": "CFF9F3",
+ "Hunter Green": "161D10",
+ "Hurricane": "877C7B",
+ "Husk": "B7A458",
+ "Ice Cold": "B1F4E7",
+ "Iceberg": "DAF4F0",
+ "Illusion": "F6A4C9",
+ "Inch Worm": "B0E313",
+ "Indian Khaki": "C3B091",
+ "Indian Tan": "4D1E01",
+ "Indigo": "4F69C6",
+ "Indochine": "C26B03",
+ "International Klein Blue": "002FA7",
+ "International Orange": "FF4F00",
+ "Irish Coffee": "5F3D26",
+ "Iroko": "433120",
+ "Iron": "D4D7D9",
+ "Ironside Gray": "676662",
+ "Ironstone": "86483C",
+ "Island Spice": "FFFCEE",
+ "Ivory": "FFFFF0",
+ "Jacaranda": "2E0329",
+ "Jacarta": "3A2A6A",
+ "Jacko Bean": "2E1905",
+ "Jacksons Purple": "20208D",
+ "Jade": "00A86B",
+ "Jaffa": "EF863F",
+ "Jagged Ice": "C2E8E5",
+ "Jagger": "350E57",
+ "Jaguar": "080110",
+ "Jambalaya": "5B3013",
+ "Janna": "F4EBD3",
+ "Japanese Laurel": "0A6906",
+ "Japanese Maple": "780109",
+ "Japonica": "D87C63",
+ "Java": "1FC2C2",
+ "Jazzberry Jam": "A50B5E",
+ "Jelly Bean": "297B9A",
+ "Jet Stream": "B5D2CE",
+ "Jewel": "126B40",
+ "Jon": "3B1F1F",
+ "Jonquil": "EEFF9A",
+ "Jordy Blue": "8AB9F1",
+ "Judge Gray": "544333",
+ "Jumbo": "7C7B82",
+ "Jungle Green": "29AB87",
+ "Jungle Mist": "B4CFD3",
+ "Juniper": "6D9292",
+ "Just Right": "ECCDB9",
+ "Kabul": "5E483E",
+ "Kaitoke Green": "004620",
+ "Kangaroo": "C6C8BD",
+ "Karaka": "1E1609",
+ "Karry": "FFEAD4",
+ "Kashmir Blue": "507096",
+ "Kelp": "454936",
+ "Kenyan Copper": "7C1C05",
+ "Keppel": "3AB09E",
+ "Key Lime Pie": "BFC921",
+ "Khaki": "F0E68C",
+ "Kidnapper": "E1EAD4",
+ "Kilamanjaro": "240C02",
+ "Killarney": "3A6A47",
+ "Kimberly": "736C9F",
+ "Kingfisher Daisy": "3E0480",
+ "Kobi": "E79FC4",
+ "Kokoda": "6E6D57",
+ "Korma": "8F4B0E",
+ "Koromiko": "FFBD5F",
+ "Kournikova": "FFE772",
+ "Kumera": "886221",
+ "La Palma": "368716",
+ "La Rioja": "B3C110",
+ "Las Palmas": "C6E610",
+ "Laser": "C8B568",
+ "Laser Lemon": "FFFF66",
+ "Laurel": "749378",
+ "Lavender": "B57EDC",
+ "Lavender Gray": "BDBBD7",
+ "Lavender Magenta": "EE82EE",
+ "Lavender Pink": "FBAED2",
+ "Lavender Purple": "967BB6",
+ "Lavender Rose": "FBA0E3",
+ "Lavender blush": "FFF0F5",
+ "Leather": "967059",
+ "Lemon": "FDE910",
+ "Lemon Chiffon": "FFFACD",
+ "Lemon Ginger": "AC9E22",
+ "Lemon Grass": "9B9E8F",
+ "Light Apricot": "FDD5B1",
+ "Light Orchid": "E29CD2",
+ "Light Wisteria": "C9A0DC",
+ "Lightning Yellow": "FCC01E",
+ "Lilac": "C8A2C8",
+ "Lilac Bush": "9874D3",
+ "Lily": "C8AABF",
+ "Lily White": "E7F8FF",
+ "Lima": "76BD17",
+ "Lime": "BFFF00",
+ "Limeade": "6F9D02",
+ "Limed Ash": "747D63",
+ "Limed Oak": "AC8A56",
+ "Limed Spruce": "394851",
+ "Linen": "FAF0E6",
+ "Link Water": "D9E4F5",
+ "Lipstick": "AB0563",
+ "Lisbon Brown": "423921",
+ "Livid Brown": "4D282E",
+ "Loafer": "EEF4DE",
+ "Loblolly": "BDC9CE",
+ "Lochinvar": "2C8C84",
+ "Lochmara": "007EC7",
+ "Locust": "A8AF8E",
+ "Log Cabin": "242A1D",
+ "Logan": "AAA9CD",
+ "Lola": "DFCFDB",
+ "London Hue": "BEA6C3",
+ "Lonestar": "6D0101",
+ "Lotus": "863C3C",
+ "Loulou": "460B41",
+ "Lucky": "AF9F1C",
+ "Lucky Point": "1A1A68",
+ "Lunar Green": "3C493A",
+ "Luxor Gold": "A7882C",
+ "Lynch": "697E9A",
+ "Mabel": "D9F7FF",
+ "Macaroni and Cheese": "FFB97B",
+ "Madang": "B7F0BE",
+ "Madison": "09255D",
+ "Madras": "3F3002",
+ "Magenta / Fuchsia": "FF00FF",
+ "Magic Mint": "AAF0D1",
+ "Magnolia": "F8F4FF",
+ "Mahogany": "4E0606",
+ "Mai Tai": "B06608",
+ "Maize": "F5D5A0",
+ "Makara": "897D6D",
+ "Mako": "444954",
+ "Malachite": "0BDA51",
+ "Malibu": "7DC8F7",
+ "Mallard": "233418",
+ "Malta": "BDB2A1",
+ "Mamba": "8E8190",
+ "Manatee": "8D90A1",
+ "Mandalay": "AD781B",
+ "Mandy": "E25465",
+ "Mandys Pink": "F2C3B2",
+ "Mango Tango": "E77200",
+ "Manhattan": "F5C999",
+ "Mantis": "74C365",
+ "Mantle": "8B9C90",
+ "Manz": "EEEF78",
+ "Mardi Gras": "350036",
+ "Marigold": "B98D28",
+ "Marigold Yellow": "FBE870",
+ "Mariner": "286ACD",
+ "Maroon": "800000",
+ "Maroon Flush": "C32148",
+ "Maroon Oak": "520C17",
+ "Marshland": "0B0F08",
+ "Martini": "AFA09E",
+ "Martinique": "363050",
+ "Marzipan": "F8DB9D",
+ "Masala": "403B38",
+ "Matisse": "1B659D",
+ "Matrix": "B05D54",
+ "Matterhorn": "4E3B41",
+ "Mauve": "E0B0FF",
+ "Mauvelous": "F091A9",
+ "Maverick": "D8C2D5",
+ "Medium Carmine": "AF4035",
+ "Medium Purple": "9370DB",
+ "Medium Red Violet": "BB3385",
+ "Melanie": "E4C2D5",
+ "Melanzane": "300529",
+ "Melon": "FEBAAD",
+ "Melrose": "C7C1FF",
+ "Mercury": "E5E5E5",
+ "Merino": "F6F0E6",
+ "Merlin": "413C37",
+ "Merlot": "831923",
+ "Metallic Bronze": "49371B",
+ "Metallic Copper": "71291D",
+ "Meteor": "D07D12",
+ "Meteorite": "3C1F76",
+ "Mexican Red": "A72525",
+ "Mid Gray": "5F5F6E",
+ "Midnight": "011635",
+ "Midnight Blue": "003366",
+ "Midnight Moss": "041004",
+ "Mikado": "2D2510",
+ "Milan": "FAFFA4",
+ "Milano Red": "B81104",
+ "Milk Punch": "FFF6D4",
+ "Millbrook": "594433",
+ "Mimosa": "F8FDD3",
+ "Mindaro": "E3F988",
+ "Mine Shaft": "323232",
+ "Mineral Green": "3F5D53",
+ "Ming": "36747D",
+ "Minsk": "3F307F",
+ "Mint Green": "98FF98",
+ "Mint Julep": "F1EEC1",
+ "Mint Tulip": "C4F4EB",
+ "Mirage": "161928",
+ "Mischka": "D1D2DD",
+ "Mist Gray": "C4C4BC",
+ "Mobster": "7F7589",
+ "Moccaccino": "6E1D14",
+ "Mocha": "782D19",
+ "Mojo": "C04737",
+ "Mona Lisa": "FFA194",
+ "Monarch": "8B0723",
+ "Mondo": "4A3C30",
+ "Mongoose": "B5A27F",
+ "Monsoon": "8A8389",
+ "Monte Carlo": "83D0C6",
+ "Monza": "C7031E",
+ "Moody Blue": "7F76D3",
+ "Moon Glow": "FCFEDA",
+ "Moon Mist": "DCDDCC",
+ "Moon Raker": "D6CEF6",
+ "Morning Glory": "9EDEE0",
+ "Morocco Brown": "441D00",
+ "Mortar": "504351",
+ "Mosque": "036A6E",
+ "Moss Green": "ADDFAD",
+ "Mountain Meadow": "1AB385",
+ "Mountain Mist": "959396",
+ "Mountbatten Pink": "997A8D",
+ "Muddy Waters": "B78E5C",
+ "Muesli": "AA8B5B",
+ "Mulberry": "C54B8C",
+ "Mulberry Wood": "5C0536",
+ "Mule Fawn": "8C472F",
+ "Mulled Wine": "4E4562",
+ "Mustard": "FFDB58",
+ "My Pink": "D69188",
+ "My Sin": "FFB31F",
+ "Mystic": "E2EBED",
+ "Nandor": "4B5D52",
+ "Napa": "ACA494",
+ "Narvik": "EDF9F1",
+ "Natural Gray": "8B8680",
+ "Navajo White": "FFDEAD",
+ "Navy Blue": "000080",
+ "Nebula": "CBDBD6",
+ "Negroni": "FFE2C5",
+ "Neon Carrot": "FF9933",
+ "Nepal": "8EABC1",
+ "Neptune": "7CB7BB",
+ "Nero": "140600",
+ "Nevada": "646E75",
+ "New Orleans": "F3D69D",
+ "New York Pink": "D7837F",
+ "Niagara": "06A189",
+ "Night Rider": "1F120F",
+ "Night Shadz": "AA375A",
+ "Nile Blue": "193751",
+ "Nobel": "B7B1B1",
+ "Nomad": "BAB1A2",
+ "Norway": "A8BD9F",
+ "Nugget": "C59922",
+ "Nutmeg": "81422C",
+ "Nutmeg Wood Finish": "683600",
+ "Oasis": "FEEFCE",
+ "Observatory": "02866F",
+ "Ocean Green": "41AA78",
+ "Ochre": "CC7722",
+ "Off Green": "E6F8F3",
+ "Off Yellow": "FEF9E3",
+ "Oil": "281E15",
+ "Old Brick": "901E1E",
+ "Old Copper": "724A2F",
+ "Old Gold": "CFB53B",
+ "Old Lace": "FDF5E6",
+ "Old Lavender": "796878",
+ "Old Rose": "C08081",
+ "Olive": "808000",
+ "Olive Drab": "6B8E23",
+ "Olive Green": "B5B35C",
+ "Olive Haze": "8B8470",
+ "Olivetone": "716E10",
+ "Olivine": "9AB973",
+ "Onahau": "CDF4FF",
+ "Onion": "2F270E",
+ "Opal": "A9C6C2",
+ "Opium": "8E6F70",
+ "Oracle": "377475",
+ "Orange": "FF681F",
+ "Orange Peel": "FFA000",
+ "Orange Roughy": "C45719",
+ "Orange White": "FEFCED",
+ "Orchid": "DA70D6",
+ "Orchid White": "FFFDF3",
+ "Oregon": "9B4703",
+ "Orient": "015E85",
+ "Oriental Pink": "C69191",
+ "Orinoco": "F3FBD4",
+ "Oslo Gray": "878D91",
+ "Ottoman": "E9F8ED",
+ "Outer Space": "2D383A",
+ "Outrageous Orange": "FF6037",
+ "Oxford Blue": "384555",
+ "Oxley": "779E86",
+ "Oyster Bay": "DAFAFF",
+ "Oyster Pink": "E9CECD",
+ "Paarl": "A65529",
+ "Pablo": "776F61",
+ "Pacific Blue": "009DC4",
+ "Pacifika": "778120",
+ "Paco": "411F10",
+ "Padua": "ADE6C4",
+ "Pale Canary": "FFFF99",
+ "Pale Leaf": "C0D3B9",
+ "Pale Oyster": "988D77",
+ "Pale Prim": "FDFEB8",
+ "Pale Rose": "FFE1F2",
+ "Pale Sky": "6E7783",
+ "Pale Slate": "C3BFC1",
+ "Palm Green": "09230F",
+ "Palm Leaf": "19330E",
+ "Pampas": "F4F2EE",
+ "Panache": "EAF6EE",
+ "Pancho": "EDCDAB",
+ "Papaya Whip": "FFEFD5",
+ "Paprika": "8D0226",
+ "Paradiso": "317D82",
+ "Parchment": "F1E9D2",
+ "Paris Daisy": "FFF46E",
+ "Paris M": "26056A",
+ "Paris White": "CADCD4",
+ "Parsley": "134F19",
+ "Pastel Green": "77DD77",
+ "Pastel Pink": "FFD1DC",
+ "Patina": "639A8F",
+ "Pattens Blue": "DEF5FF",
+ "Paua": "260368",
+ "Pavlova": "D7C498",
+ "Peach": "FFE5B4",
+ "Peach Cream": "FFF0DB",
+ "Peach Orange": "FFCC99",
+ "Peach Schnapps": "FFDCD6",
+ "Peach Yellow": "FADFAD",
+ "Peanut": "782F16",
+ "Pear": "D1E231",
+ "Pearl Bush": "E8E0D5",
+ "Pearl Lusta": "FCF4DC",
+ "Peat": "716B56",
+ "Pelorous": "3EABBF",
+ "Peppermint": "E3F5E1",
+ "Perano": "A9BEF2",
+ "Perfume": "D0BEF8",
+ "Periglacial Blue": "E1E6D6",
+ "Periwinkle": "CCCCFF",
+ "Periwinkle Gray": "C3CDE6",
+ "Persian Blue": "1C39BB",
+ "Persian Green": "00A693",
+ "Persian Indigo": "32127A",
+ "Persian Pink": "F77FBE",
+ "Persian Plum": "701C1C",
+ "Persian Red": "CC3333",
+ "Persian Rose": "FE28A2",
+ "Persimmon": "FF6B53",
+ "Peru Tan": "7F3A02",
+ "Pesto": "7C7631",
+ "Petite Orchid": "DB9690",
+ "Pewter": "96A8A1",
+ "Pharlap": "A3807B",
+ "Picasso": "FFF39D",
+ "Pickled Bean": "6E4826",
+ "Pickled Bluewood": "314459",
+ "Picton Blue": "45B1E8",
+ "Pig Pink": "FDD7E4",
+ "Pigeon Post": "AFBDD9",
+ "Pigment Indigo": "4B0082",
+ "Pine Cone": "6D5E54",
+ "Pine Glade": "C7CD90",
+ "Pine Green": "01796F",
+ "Pine Tree": "171F04",
+ "Pink": "FFC0CB",
+ "Pink Flamingo": "FF66FF",
+ "Pink Flare": "E1C0C8",
+ "Pink Lace": "FFDDF4",
+ "Pink Lady": "FFF1D8",
+ "Pink Salmon": "FF91A4",
+ "Pink Swan": "BEB5B7",
+ "Piper": "C96323",
+ "Pipi": "FEF4CC",
+ "Pippin": "FFE1DF",
+ "Pirate Gold": "BA7F03",
+ "Pistachio": "9DC209",
+ "Pixie Green": "C0D8B6",
+ "Pizazz": "FF9000",
+ "Pizza": "C99415",
+ "Plantation": "27504B",
+ "Plum": "843179",
+ "Pohutukawa": "8F021C",
+ "Polar": "E5F9F6",
+ "Polo Blue": "8DA8CC",
+ "Pomegranate": "F34723",
+ "Pompadour": "660045",
+ "Porcelain": "EFF2F3",
+ "Porsche": "EAAE69",
+ "Port Gore": "251F4F",
+ "Portafino": "FFFFB4",
+ "Portage": "8B9FEE",
+ "Portica": "F9E663",
+ "Pot Pourri": "F5E7E2",
+ "Potters Clay": "8C5738",
+ "Powder Ash": "BCC9C2",
+ "Powder Blue": "B0E0E6",
+ "Prairie Sand": "9A3820",
+ "Prelude": "D0C0E5",
+ "Prim": "F0E2EC",
+ "Primrose": "EDEA99",
+ "Provincial Pink": "FEF5F1",
+ "Prussian Blue": "003153",
+ "Puce": "CC8899",
+ "Pueblo": "7D2C14",
+ "Puerto Rico": "3FC1AA",
+ "Pumice": "C2CAC4",
+ "Pumpkin": "FF7518",
+ "Pumpkin Skin": "B1610B",
+ "Punch": "DC4333",
+ "Punga": "4D3D14",
+ "Purple": "660099",
+ "Purple Heart": "652DC1",
+ "Purple Mountain's Majesty": "9678B6",
+ "Purple Pizzazz": "FF00CC",
+ "Putty": "E7CD8C",
+ "Quarter Pearl Lusta": "FFFDF4",
+ "Quarter Spanish White": "F7F2E1",
+ "Quicksand": "BD978E",
+ "Quill Gray": "D6D6D1",
+ "Quincy": "623F2D",
+ "Racing Green": "0C1911",
+ "Radical Red": "FF355E",
+ "Raffia": "EADAB8",
+ "Rainee": "B9C8AC",
+ "Rajah": "F7B668",
+ "Rangitoto": "2E3222",
+ "Rangoon Green": "1C1E13",
+ "Raven": "727B89",
+ "Raw Sienna": "D27D46",
+ "Raw Umber": "734A12",
+ "Razzle Dazzle Rose": "FF33CC",
+ "Razzmatazz": "E30B5C",
+ "Rebel": "3C1206",
+ "Red": "FF0000",
+ "Red Beech": "7B3801",
+ "Red Berry": "8E0000",
+ "Red Damask": "DA6A41",
+ "Red Devil": "860111",
+ "Red Orange": "FF3F34",
+ "Red Oxide": "6E0902",
+ "Red Ribbon": "ED0A3F",
+ "Red Robin": "80341F",
+ "Red Stage": "D05F04",
+ "Red Violet": "C71585",
+ "Redwood": "5D1E0F",
+ "Reef": "C9FFA2",
+ "Reef Gold": "9F821C",
+ "Regal Blue": "013F6A",
+ "Regent Gray": "86949F",
+ "Regent St Blue": "AAD6E6",
+ "Remy": "FEEBF3",
+ "Reno Sand": "A86515",
+ "Resolution Blue": "002387",
+ "Revolver": "2C1632",
+ "Rhino": "2E3F62",
+ "Rice Cake": "FFFEF0",
+ "Rice Flower": "EEFFE2",
+ "Rich Gold": "A85307",
+ "Rio Grande": "BBD009",
+ "Ripe Lemon": "F4D81C",
+ "Ripe Plum": "410056",
+ "Riptide": "8BE6D8",
+ "River Bed": "434C59",
+ "Rob Roy": "EAC674",
+ "Robin's Egg Blue": "00CCCC",
+ "Rock": "4D3833",
+ "Rock Blue": "9EB1CD",
+ "Rock Spray": "BA450C",
+ "Rodeo Dust": "C9B29B",
+ "Rolling Stone": "747D83",
+ "Roman": "DE6360",
+ "Roman Coffee": "795D4C",
+ "Romance": "FFFEFD",
+ "Romantic": "FFD2B7",
+ "Ronchi": "ECC54E",
+ "Roof Terracotta": "A62F20",
+ "Rope": "8E4D1E",
+ "Rose": "FF007F",
+ "Rose Bud": "FBB2A3",
+ "Rose Bud Cherry": "800B47",
+ "Rose Fog": "E7BCB4",
+ "Rose White": "FFF6F5",
+ "Rose of Sharon": "BF5500",
+ "Rosewood": "65000B",
+ "Roti": "C6A84B",
+ "Rouge": "A23B6C",
+ "Royal Blue": "4169E1",
+ "Royal Heath": "AB3472",
+ "Royal Purple": "6B3FA0",
+ "Rum": "796989",
+ "Rum Swizzle": "F9F8E4",
+ "Russet": "80461B",
+ "Russett": "755A57",
+ "Rust": "B7410E",
+ "Rustic Red": "480404",
+ "Rusty Nail": "86560A",
+ "Saddle": "4C3024",
+ "Saddle Brown": "583401",
+ "Saffron": "F4C430",
+ "Saffron Mango": "F9BF58",
+ "Sage": "9EA587",
+ "Sahara": "B7A214",
+ "Sahara Sand": "F1E788",
+ "Sail": "B8E0F9",
+ "Salem": "097F4B",
+ "Salmon": "FF8C69",
+ "Salomie": "FEDB8D",
+ "Salt Box": "685E6E",
+ "Saltpan": "F1F7F2",
+ "Sambuca": "3A2010",
+ "San Felix": "0B6207",
+ "San Juan": "304B6A",
+ "San Marino": "456CAC",
+ "Sand Dune": "826F65",
+ "Sandal": "AA8D6F",
+ "Sandrift": "AB917A",
+ "Sandstone": "796D62",
+ "Sandwisp": "F5E7A2",
+ "Sandy Beach": "FFEAC8",
+ "Sandy brown": "F4A460",
+ "Sangria": "92000A",
+ "Sanguine Brown": "8D3D38",
+ "Santa Fe": "B16D52",
+ "Santas Gray": "9FA0B1",
+ "Sapling": "DED4A4",
+ "Sapphire": "2F519E",
+ "Saratoga": "555B10",
+ "Satin Linen": "E6E4D4",
+ "Sauvignon": "FFF5F3",
+ "Sazerac": "FFF4E0",
+ "Scampi": "675FA6",
+ "Scandal": "CFFAF4",
+ "Scarlet": "FF2400",
+ "Scarlet Gum": "431560",
+ "Scarlett": "950015",
+ "Scarpa Flow": "585562",
+ "Schist": "A9B497",
+ "School bus Yellow": "FFD800",
+ "Schooner": "8B847E",
+ "Science Blue": "0066CC",
+ "Scooter": "2EBFD4",
+ "Scorpion": "695F62",
+ "Scotch Mist": "FFFBDC",
+ "Screamin' Green": "66FF66",
+ "Sea Buckthorn": "FBA129",
+ "Sea Green": "2E8B57",
+ "Sea Mist": "C5DBCA",
+ "Sea Nymph": "78A39C",
+ "Sea Pink": "ED989E",
+ "Seagull": "80CCEA",
+ "Seance": "731E8F",
+ "Seashell": "F1F1F1",
+ "Seashell Peach": "FFF5EE",
+ "Seaweed": "1B2F11",
+ "Selago": "F0EEFD",
+ "Selective Yellow": "FFBA00",
+ "Sepia": "704214",
+ "Sepia Black": "2B0202",
+ "Sepia Skin": "9E5B40",
+ "Serenade": "FFF4E8",
+ "Shadow": "837050",
+ "Shadow Green": "9AC2B8",
+ "Shady Lady": "AAA5A9",
+ "Shakespeare": "4EABD1",
+ "Shalimar": "FBFFBA",
+ "Shamrock": "33CC99",
+ "Shark": "25272C",
+ "Sherpa Blue": "004950",
+ "Sherwood Green": "02402C",
+ "Shilo": "E8B9B3",
+ "Shingle Fawn": "6B4E31",
+ "Ship Cove": "788BBA",
+ "Ship Gray": "3E3A44",
+ "Shiraz": "B20931",
+ "Shocking": "E292C0",
+ "Shocking Pink": "FC0FC0",
+ "Shuttle Gray": "5F6672",
+ "Siam": "646A54",
+ "Sidecar": "F3E7BB",
+ "Silk": "BDB1A8",
+ "Silver": "C0C0C0",
+ "Silver Chalice": "ACACAC",
+ "Silver Rust": "C9C0BB",
+ "Silver Sand": "BFC1C2",
+ "Silver Tree": "66B58F",
+ "Sinbad": "9FD7D3",
+ "Siren": "7A013A",
+ "Sirocco": "718080",
+ "Sisal": "D3CBBA",
+ "Skeptic": "CAE6DA",
+ "Sky Blue": "76D7EA",
+ "Slate Gray": "708090",
+ "Smalt": "003399",
+ "Smalt Blue": "51808F",
+ "Smoky": "605B73",
+ "Snow Drift": "F7FAF7",
+ "Snow Flurry": "E4FFD1",
+ "Snowy Mint": "D6FFDB",
+ "Snuff": "E2D8ED",
+ "Soapstone": "FFFBF9",
+ "Soft Amber": "D1C6B4",
+ "Soft Peach": "F5EDEF",
+ "Solid Pink": "893843",
+ "Solitaire": "FEF8E2",
+ "Solitude": "EAF6FF",
+ "Sorbus": "FD7C07",
+ "Sorrell Brown": "CEB98F",
+ "Soya Bean": "6A6051",
+ "Spanish Green": "819885",
+ "Spectra": "2F5A57",
+ "Spice": "6A442E",
+ "Spicy Mix": "885342",
+ "Spicy Mustard": "74640D",
+ "Spicy Pink": "816E71",
+ "Spindle": "B6D1EA",
+ "Spray": "79DEEC",
+ "Spring Green": "00FF7F",
+ "Spring Leaves": "578363",
+ "Spring Rain": "ACCBB1",
+ "Spring Sun": "F6FFDC",
+ "Spring Wood": "F8F6F1",
+ "Sprout": "C1D7B0",
+ "Spun Pearl": "AAABB7",
+ "Squirrel": "8F8176",
+ "St Tropaz": "2D569B",
+ "Stack": "8A8F8A",
+ "Star Dust": "9F9F9C",
+ "Stark White": "E5D7BD",
+ "Starship": "ECF245",
+ "Steel Blue": "4682B4",
+ "Steel Gray": "262335",
+ "Stiletto": "9C3336",
+ "Stonewall": "928573",
+ "Storm Dust": "646463",
+ "Storm Gray": "717486",
+ "Stratos": "000741",
+ "Straw": "D4BF8D",
+ "Strikemaster": "956387",
+ "Stromboli": "325D52",
+ "Studio": "714AB2",
+ "Submarine": "BAC7C9",
+ "Sugar Cane": "F9FFF6",
+ "Sulu": "C1F07C",
+ "Summer Green": "96BBAB",
+ "Sun": "FBAC13",
+ "Sundance": "C9B35B",
+ "Sundown": "FFB1B3",
+ "Sunflower": "E4D422",
+ "Sunglo": "E16865",
+ "Sunglow": "FFCC33",
+ "Sunset Orange": "FE4C40",
+ "Sunshade": "FF9E2C",
+ "Supernova": "FFC901",
+ "Surf": "BBD7C1",
+ "Surf Crest": "CFE5D2",
+ "Surfie Green": "0C7A79",
+ "Sushi": "87AB39",
+ "Suva Gray": "888387",
+ "Swamp": "001B1C",
+ "Swamp Green": "ACB78E",
+ "Swans Down": "DCF0EA",
+ "Sweet Corn": "FBEA8C",
+ "Sweet Pink": "FD9FA2",
+ "Swirl": "D3CDC5",
+ "Swiss Coffee": "DDD6D5",
+ "Sycamore": "908D39",
+ "Tabasco": "A02712",
+ "Tacao": "EDB381",
+ "Tacha": "D6C562",
+ "Tahiti Gold": "E97C07",
+ "Tahuna Sands": "EEF0C8",
+ "Tall Poppy": "B32D29",
+ "Tallow": "A8A589",
+ "Tamarillo": "991613",
+ "Tamarind": "341515",
+ "Tan": "D2B48C",
+ "Tan Hide": "FA9D5A",
+ "Tana": "D9DCC1",
+ "Tangaroa": "03163C",
+ "Tangerine": "F28500",
+ "Tango": "ED7A1C",
+ "Tapa": "7B7874",
+ "Tapestry": "B05E81",
+ "Tara": "E1F6E8",
+ "Tarawera": "073A50",
+ "Tasman": "CFDCCF",
+ "Taupe": "483C32",
+ "Taupe Gray": "B3AF95",
+ "Tawny Port": "692545",
+ "Te Papa Green": "1E433C",
+ "Tea": "C1BAB0",
+ "Tea Green": "D0F0C0",
+ "Teak": "B19461",
+ "Teal": "008080",
+ "Teal Blue": "044259",
+ "Temptress": "3B000B",
+ "Tenn": "CD5700",
+ "Tequila": "FFE6C7",
+ "Terracotta": "E2725B",
+ "Texas": "F8F99C",
+ "Texas Rose": "FFB555",
+ "Thatch": "B69D98",
+ "Thatch Green": "403D19",
+ "Thistle": "D8BFD8",
+ "Thistle Green": "CCCAA8",
+ "Thunder": "33292F",
+ "Thunderbird": "C02B18",
+ "Tia Maria": "C1440E",
+ "Tiara": "C3D1D1",
+ "Tiber": "063537",
+ "Tickle Me Pink": "FC80A5",
+ "Tidal": "F1FFAD",
+ "Tide": "BFB8B0",
+ "Timber Green": "16322C",
+ "Timberwolf": "D9D6CF",
+ "Titan White": "F0EEFF",
+ "Toast": "9A6E61",
+ "Tobacco Brown": "715D47",
+ "Toledo": "3A0020",
+ "Tolopea": "1B0245",
+ "Tom Thumb": "3F583B",
+ "Tonys Pink": "E79F8C",
+ "Topaz": "7C778A",
+ "Torch Red": "FD0E35",
+ "Torea Bay": "0F2D9E",
+ "Tory Blue": "1450AA",
+ "Tosca": "8D3F3F",
+ "Totem Pole": "991B07",
+ "Tower Gray": "A9BDBF",
+ "Tradewind": "5FB3AC",
+ "Tranquil": "E6FFFF",
+ "Travertine": "FFFDE8",
+ "Tree Poppy": "FC9C1D",
+ "Treehouse": "3B2820",
+ "Trendy Green": "7C881A",
+ "Trendy Pink": "8C6495",
+ "Trinidad": "E64E03",
+ "Tropical Blue": "C3DDF9",
+ "Tropical Rain Forest": "00755E",
+ "Trout": "4A4E5A",
+ "True V": "8A73D6",
+ "Tuatara": "363534",
+ "Tuft Bush": "FFDDCD",
+ "Tulip Tree": "EAB33B",
+ "Tumbleweed": "DEA681",
+ "Tuna": "353542",
+ "Tundora": "4A4244",
+ "Turbo": "FAE600",
+ "Turkish Rose": "B57281",
+ "Turmeric": "CABB48",
+ "Turquoise": "30D5C8",
+ "Turquoise Blue": "6CDAE7",
+ "Turtle Green": "2A380B",
+ "Tuscany": "BD5E2E",
+ "Tusk": "EEF3C3",
+ "Tussock": "C5994B",
+ "Tutu": "FFF1F9",
+ "Twilight": "E4CFDE",
+ "Twilight Blue": "EEFDFF",
+ "Twine": "C2955D",
+ "Tyrian Purple": "66023C",
+ "Ultramarine": "120A8F",
+ "Valencia": "D84437",
+ "Valentino": "350E42",
+ "Valhalla": "2B194F",
+ "Van Cleef": "49170C",
+ "Vanilla": "D1BEA8",
+ "Vanilla Ice": "F3D9DF",
+ "Varden": "FFF6DF",
+ "Venetian Red": "72010F",
+ "Venice Blue": "055989",
+ "Venus": "928590",
+ "Verdigris": "5D5E37",
+ "Verdun Green": "495400",
+ "Vermilion": "FF4D00",
+ "Vesuvius": "B14A0B",
+ "Victoria": "534491",
+ "Vida Loca": "549019",
+ "Viking": "64CCDB",
+ "Vin Rouge": "983D61",
+ "Viola": "CB8FA9",
+ "Violent Violet": "290C5E",
+ "Violet": "240A40",
+ "Violet Eggplant": "991199",
+ "Violet Red": "F7468A",
+ "Viridian": "40826D",
+ "Viridian Green": "678975",
+ "Vis Vis": "FFEFA1",
+ "Vista Blue": "8FD6B4",
+ "Vista White": "FCF8F7",
+ "Vivid Tangerine": "FF9980",
+ "Vivid Violet": "803790",
+ "Voodoo": "533455",
+ "Vulcan": "10121D",
+ "Wafer": "DECBC6",
+ "Waikawa Gray": "5A6E9C",
+ "Waiouru": "363C0D",
+ "Walnut": "773F1A",
+ "Wasabi": "788A25",
+ "Water Leaf": "A1E9DE",
+ "Watercourse": "056F57",
+ "Waterloo ": "7B7C94",
+ "Wattle": "DCD747",
+ "Watusi": "FFDDCF",
+ "Wax Flower": "FFC0A8",
+ "We Peep": "F7DBE6",
+ "Web Orange": "FFA500",
+ "Wedgewood": "4E7F9E",
+ "Well Read": "B43332",
+ "West Coast": "625119",
+ "West Side": "FF910F",
+ "Westar": "DCD9D2",
+ "Wewak": "F19BAB",
+ "Wheat": "F5DEB3",
+ "Wheatfield": "F3EDCF",
+ "Whiskey": "D59A6F",
+ "Whisper": "F7F5FA",
+ "White": "FFFFFF",
+ "White Ice": "DDF9F1",
+ "White Lilac": "F8F7FC",
+ "White Linen": "F8F0E8",
+ "White Pointer": "FEF8FF",
+ "White Rock": "EAE8D4",
+ "Wild Blue Yonder": "7A89B8",
+ "Wild Rice": "ECE090",
+ "Wild Sand": "F4F4F4",
+ "Wild Strawberry": "FF3399",
+ "Wild Watermelon": "FD5B78",
+ "Wild Willow": "B9C46A",
+ "William": "3A686C",
+ "Willow Brook": "DFECDA",
+ "Willow Grove": "65745D",
+ "Windsor": "3C0878",
+ "Wine Berry": "591D35",
+ "Winter Hazel": "D5D195",
+ "Wisp Pink": "FEF4F8",
+ "Wisteria": "9771B5",
+ "Wistful": "A4A6D3",
+ "Witch Haze": "FFFC99",
+ "Wood Bark": "261105",
+ "Woodland": "4D5328",
+ "Woodrush": "302A0F",
+ "Woodsmoke": "0C0D0F",
+ "Woody Brown": "483131",
+ "Xanadu": "738678",
+ "Yellow": "FFFF00",
+ "Yellow Green": "C5E17A",
+ "Yellow Metal": "716338",
+ "Yellow Orange": "FFAE42",
+ "Yellow Sea": "FEA904",
+ "Your Pink": "FFC3C0",
+ "Yukon Gold": "7B6608",
+ "Yuma": "CEC291",
+ "Zambezi": "685558",
+ "Zanah": "DAECD6",
+ "Zest": "E5841B",
+ "Zeus": "292319",
+ "Ziggurat": "BFDBE2",
+ "Zinnwaldite": "EBC2AF",
+ "Zircon": "F4F8FF",
+ "Zombie": "E4D69B",
+ "Zorba": "A59B91",
+ "Zuccini": "044022",
+ "Zumthor": "EDF6FF"
+}
diff --git a/bot/resources/utilities/starter.yaml b/bot/resources/utilities/starter.yaml
index 6b0de0ef..ce759e1a 100644
--- a/bot/resources/utilities/starter.yaml
+++ b/bot/resources/utilities/starter.yaml
@@ -32,8 +32,6 @@
- How many years have you spent coding?
- What book do you highly recommend everyone to read?
- What websites do you use daily to keep yourself up to date with the industry?
-- What made you want to join this Discord server?
-- How are you?
- What is the best advice you have ever gotten in regards to programming/software?
- What is the most satisfying thing you've done in your life?
- Who is your favorite music composer/producer/singer?
@@ -49,3 +47,4 @@
- What artistic talents do you have?
- What is the tallest building you've entered?
- What is the oldest computer you've ever used?
+- What animals do you like?
diff --git a/bot/resources/utilities/wtf_python_logo.jpg b/bot/resources/utilities/wtf_python_logo.jpg
new file mode 100644
index 00000000..851d7f9a
--- /dev/null
+++ b/bot/resources/utilities/wtf_python_logo.jpg
Binary files differ
diff --git a/bot/utils/checks.py b/bot/utils/checks.py
index 612d1ed6..5433f436 100644
--- a/bot/utils/checks.py
+++ b/bot/utils/checks.py
@@ -4,14 +4,7 @@ from collections.abc import Container, Iterable
from typing import Callable, Optional
from discord.ext.commands import (
- BucketType,
- CheckFailure,
- Cog,
- Command,
- CommandOnCooldown,
- Context,
- Cooldown,
- CooldownMapping,
+ BucketType, CheckFailure, Cog, Command, CommandOnCooldown, Context, Cooldown, CooldownMapping
)
from bot import constants
@@ -40,7 +33,7 @@ def in_whitelist_check(
channels: Container[int] = (),
categories: Container[int] = (),
roles: Container[int] = (),
- redirect: Optional[int] = constants.Channels.community_bot_commands,
+ redirect: Optional[int] = constants.Channels.sir_lancebot_playground,
fail_silently: bool = False,
) -> bool:
"""
diff --git a/bot/utils/decorators.py b/bot/utils/decorators.py
index 132aaa87..8954e016 100644
--- a/bot/utils/decorators.py
+++ b/bot/utils/decorators.py
@@ -196,15 +196,14 @@ def whitelist_check(**default_kwargs: Container[int]) -> Callable[[Context], boo
If `whitelist_override` is present, it is added to the global whitelist.
"""
def predicate(ctx: Context) -> bool:
- # Skip DM invocations
- if not ctx.guild:
- log.debug(f"{ctx.author} tried to use the '{ctx.command.name}' command from a DM.")
- return True
-
kwargs = default_kwargs.copy()
+ allow_dms = False
# Update kwargs based on override
if hasattr(ctx.command.callback, "override"):
+ # Handle DM invocations
+ allow_dms = ctx.command.callback.override_dm
+
# Remove default kwargs if reset is True
if ctx.command.callback.override_reset:
kwargs = {}
@@ -234,8 +233,12 @@ def whitelist_check(**default_kwargs: Container[int]) -> Callable[[Context], boo
f"invoked by {ctx.author}."
)
- log.trace(f"Calling whitelist check for {ctx.author} for command {ctx.command.name}.")
- result = in_whitelist_check(ctx, fail_silently=True, **kwargs)
+ if ctx.guild is None:
+ log.debug(f"{ctx.author} tried using the '{ctx.command.name}' command from a DM.")
+ result = allow_dms
+ else:
+ log.trace(f"Calling whitelist check for {ctx.author} for command {ctx.command.name}.")
+ result = in_whitelist_check(ctx, fail_silently=True, **kwargs)
# Return if check passed
if result:
@@ -254,14 +257,14 @@ def whitelist_check(**default_kwargs: Container[int]) -> Callable[[Context], boo
channels = set(kwargs.get("channels") or {})
categories = kwargs.get("categories")
- # Only output override channels + community_bot_commands
+ # Only output override channels + sir_lancebot_playground
if channels:
default_whitelist_channels = set(WHITELISTED_CHANNELS)
- default_whitelist_channels.discard(Channels.community_bot_commands)
+ default_whitelist_channels.discard(Channels.sir_lancebot_playground)
channels.difference_update(default_whitelist_channels)
- # Add all whitelisted category channels
- if categories:
+ # Add all whitelisted category channels, but skip if we're in DMs
+ if categories and ctx.guild is not None:
for category_id in categories:
category = ctx.guild.get_channel(category_id)
if category is None:
@@ -280,18 +283,22 @@ def whitelist_check(**default_kwargs: Container[int]) -> Callable[[Context], boo
return predicate
-def whitelist_override(bypass_defaults: bool = False, **kwargs: Container[int]) -> Callable:
+def whitelist_override(bypass_defaults: bool = False, allow_dm: bool = False, **kwargs: Container[int]) -> Callable:
"""
Override global whitelist context, with the kwargs specified.
All arguments from `in_whitelist_check` are supported, with the exception of `fail_silently`.
Set `bypass_defaults` to True if you want to completely bypass global checks.
+ Set `allow_dm` to True if you want to allow the command to be invoked from within direct messages.
+ Note that you have to be careful with any references to the guild.
+
This decorator has to go before (below) below the `command` decorator.
"""
def inner(func: Callable) -> Callable:
func.override = kwargs
func.override_reset = bypass_defaults
+ func.override_dm = allow_dm
return func
return inner
diff --git a/bot/utils/exceptions.py b/bot/utils/exceptions.py
index bf0e5813..3cd96325 100644
--- a/bot/utils/exceptions.py
+++ b/bot/utils/exceptions.py
@@ -15,3 +15,10 @@ class APIError(Exception):
self.api = api
self.status_code = status_code
self.error_msg = error_msg
+
+
+class MovedCommandError(Exception):
+ """Raised when a command has moved locations."""
+
+ def __init__(self, new_command_name: str):
+ self.new_command_name = new_command_name
diff --git a/bot/utils/halloween/spookifications.py b/bot/utils/halloween/spookifications.py
index 93c5ddb9..c45ef8dc 100644
--- a/bot/utils/halloween/spookifications.py
+++ b/bot/utils/halloween/spookifications.py
@@ -1,8 +1,7 @@
import logging
from random import choice, randint
-from PIL import Image
-from PIL import ImageOps
+from PIL import Image, ImageOps
log = logging.getLogger()
diff --git a/bot/utils/members.py b/bot/utils/members.py
new file mode 100644
index 00000000..de5850ca
--- /dev/null
+++ b/bot/utils/members.py
@@ -0,0 +1,47 @@
+import logging
+import typing as t
+
+import discord
+
+log = logging.getLogger(__name__)
+
+
+async def get_or_fetch_member(guild: discord.Guild, member_id: int) -> t.Optional[discord.Member]:
+ """
+ Attempt to get a member from cache; on failure fetch from the API.
+
+ Return `None` to indicate the member could not be found.
+ """
+ if member := guild.get_member(member_id):
+ log.trace("%s retrieved from cache.", member)
+ else:
+ try:
+ member = await guild.fetch_member(member_id)
+ except discord.errors.NotFound:
+ log.trace("Failed to fetch %d from API.", member_id)
+ return None
+ log.trace("%s fetched from API.", member)
+ return member
+
+
+async def handle_role_change(
+ member: discord.Member,
+ coro: t.Callable[..., t.Coroutine],
+ role: discord.Role
+) -> None:
+ """
+ Change `member`'s cooldown role via awaiting `coro` and handle errors.
+
+ `coro` is intended to be `discord.Member.add_roles` or `discord.Member.remove_roles`.
+ """
+ try:
+ await coro(role)
+ except discord.NotFound:
+ log.debug(f"Failed to change role for {member} ({member.id}): member not found")
+ except discord.Forbidden:
+ log.error(
+ f"Forbidden to change role for {member} ({member.id}); "
+ f"possibly due to role hierarchy"
+ )
+ except discord.HTTPException as e:
+ log.error(f"Failed to change role for {member} ({member.id}): {e.status} {e.code}")
diff --git a/bot/utils/pagination.py b/bot/utils/pagination.py
index 013ef9e7..188b279f 100644
--- a/bot/utils/pagination.py
+++ b/bot/utils/pagination.py
@@ -211,8 +211,6 @@ class LinePaginator(Paginator):
log.debug(f"Got first page reaction - changing to page 1/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
@@ -226,8 +224,6 @@ class LinePaginator(Paginator):
log.debug(f"Got last page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
embed.set_footer(text=f"{footer_text} (Page {current_page + 1}/{len(paginator.pages)})")
@@ -245,8 +241,6 @@ class LinePaginator(Paginator):
current_page -= 1
log.debug(f"Got previous page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
@@ -266,8 +260,6 @@ class LinePaginator(Paginator):
current_page += 1
log.debug(f"Got next page reaction - changing to page {current_page + 1}/{len(paginator.pages)}")
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
if footer_text:
@@ -428,8 +420,6 @@ class ImagePaginator(Paginator):
reaction_type = "next"
# Magic happens here, after page and reaction_type is set
- embed.description = ""
- await message.edit(embed=embed)
embed.description = paginator.pages[current_page]
image = paginator.images[current_page] or EmptyEmbed
diff --git a/docker-compose.yml b/docker-compose.yml
index cef49213..34408e82 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,6 +11,7 @@ services:
dockerfile: Dockerfile
container_name: sir-lancebot
init: true
+ tty: true
depends_on:
- redis
diff --git a/poetry.lock b/poetry.lock
index 289f2039..5c52a535 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -76,33 +76,36 @@ python-versions = ">=3.5.3"
[[package]]
name = "attrs"
-version = "21.2.0"
+version = "21.3.0"
description = "Classes Without Boilerplate"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
+dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"]
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
+tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"]
[[package]]
-name = "backports.entry-points-selectable"
-version = "1.1.0"
-description = "Compatibility shim providing selectable entry points for older implementations"
-category = "dev"
+name = "beautifulsoup4"
+version = "4.10.0"
+description = "Screen-scraping library"
+category = "main"
optional = false
-python-versions = ">=2.7"
+python-versions = ">3.0.0"
+
+[package.dependencies]
+soupsieve = ">1.2"
[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
[[package]]
name = "certifi"
-version = "2021.5.30"
+version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@@ -110,7 +113,7 @@ python-versions = "*"
[[package]]
name = "cffi"
-version = "1.14.6"
+version = "1.15.0"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
@@ -139,20 +142,37 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
-category = "dev"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
-name = "cycler"
-version = "0.10.0"
-description = "Composable style cycles"
+name = "coloredlogs"
+version = "15.0.1"
+description = "Colored terminal output for Python's logging module"
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+humanfriendly = ">=9.1"
+
+[package.extras]
+cron = ["capturer (>=2.4)"]
+
+[[package]]
+name = "deprecated"
+version = "1.2.13"
+description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
-six = "*"
+wrapt = ">=1.10,<2"
+
+[package.extras]
+dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"]
[[package]]
name = "discord.py"
@@ -173,15 +193,27 @@ voice = ["PyNaCl (>=1.3.0,<1.5)"]
[package.source]
type = "url"
url = "https://github.com/Rapptz/discord.py/archive/45d498c1b76deaf3b394d17ccf56112fa691d160.zip"
+
[[package]]
name = "distlib"
-version = "0.3.2"
+version = "0.3.4"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
[[package]]
+name = "emoji"
+version = "1.6.3"
+description = "Emoji for Python"
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.extras]
+dev = ["pytest", "coverage", "coveralls"]
+
+[[package]]
name = "emojis"
version = "0.6.0"
description = "Emojis for Python"
@@ -191,14 +223,15 @@ python-versions = "*"
[[package]]
name = "fakeredis"
-version = "1.6.0"
+version = "1.7.0"
description = "Fake implementation of redis API for testing purposes."
category = "main"
optional = false
python-versions = ">=3.5"
[package.dependencies]
-redis = "<3.6.0"
+packaging = "*"
+redis = "<4.1.0"
six = ">=1.12"
sortedcontainers = "*"
@@ -208,11 +241,15 @@ lua = ["lupa"]
[[package]]
name = "filelock"
-version = "3.0.12"
+version = "3.4.2"
description = "A platform independent file lock."
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
+testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
[[package]]
name = "flake8"
@@ -229,14 +266,14 @@ pyflakes = ">=2.3.0,<2.4.0"
[[package]]
name = "flake8-annotations"
-version = "2.6.2"
+version = "2.7.0"
description = "Flake8 Type Annotation Checks"
category = "dev"
optional = false
-python-versions = ">=3.6.1,<4.0.0"
+python-versions = ">=3.6.2,<4.0.0"
[package.dependencies]
-flake8 = ">=3.7,<4.0"
+flake8 = ">=3.7,<5.0"
[[package]]
name = "flake8-bugbear"
@@ -266,15 +303,20 @@ flake8 = ">=3"
pydocstyle = ">=2.1"
[[package]]
-name = "flake8-import-order"
-version = "0.18.1"
-description = "Flake8 and pylama plugin that checks the ordering of import statements."
+name = "flake8-isort"
+version = "4.1.1"
+description = "flake8 plugin that integrates isort ."
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
-pycodestyle = "*"
+flake8 = ">=3.2.1,<5"
+isort = ">=4.3.5,<6"
+testfixtures = ">=6.8.0,<7"
+
+[package.extras]
+test = ["pytest-cov"]
[[package]]
name = "flake8-polyfill"
@@ -300,14 +342,14 @@ flake8 = "*"
[[package]]
name = "flake8-tidy-imports"
-version = "4.4.1"
+version = "4.5.0"
description = "A flake8 plugin that helps you write tidier imports."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-flake8 = ">=3.8.0,<4"
+flake8 = ">=3.8.0,<5"
[[package]]
name = "flake8-todo"
@@ -329,47 +371,62 @@ optional = false
python-versions = ">=3.6"
[[package]]
+name = "humanfriendly"
+version = "10.0"
+description = "Human friendly output for text interfaces using Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""}
+
+[[package]]
name = "identify"
-version = "2.2.13"
+version = "2.4.1"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.6.1"
[package.extras]
-license = ["editdistance-s"]
+license = ["ukkonen"]
[[package]]
name = "idna"
-version = "3.2"
+version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
-name = "kiwisolver"
-version = "1.3.2"
-description = "A fast implementation of the Cassowary constraint solver"
-category = "main"
+name = "isort"
+version = "5.10.1"
+description = "A Python utility / library to sort Python imports."
+category = "dev"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6.1,<4.0"
+
+[package.extras]
+pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
+requirements_deprecated_finder = ["pipreqs", "pip-api"]
+colors = ["colorama (>=0.4.3,<0.5.0)"]
+plugins = ["setuptools"]
[[package]]
-name = "matplotlib"
-version = "3.4.3"
-description = "Python plotting package"
+name = "lxml"
+version = "4.7.1"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
-[package.dependencies]
-cycler = ">=0.10"
-kiwisolver = ">=1.0.1"
-numpy = ">=1.16"
-pillow = ">=6.2.0"
-pyparsing = ">=2.2.1"
-python-dateutil = ">=2.7"
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html5 = ["html5lib"]
+htmlsoup = ["beautifulsoup4"]
+source = ["Cython (>=0.29.7)"]
[[package]]
name = "mccabe"
@@ -389,7 +446,7 @@ python-versions = ">=3.5"
[[package]]
name = "multidict"
-version = "5.1.0"
+version = "5.2.0"
description = "multidict implementation"
category = "main"
optional = false
@@ -404,12 +461,15 @@ optional = false
python-versions = "*"
[[package]]
-name = "numpy"
-version = "1.21.1"
-description = "NumPy is the fundamental package for array computing with Python."
+name = "packaging"
+version = "21.3"
+description = "Core utilities for Python packages"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
+
+[package.dependencies]
+pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pep8-naming"
@@ -425,15 +485,15 @@ flake8-polyfill = ">=1.0.2,<2"
[[package]]
name = "pillow"
-version = "8.3.2"
+version = "9.0.1"
description = "Python Imaging Library (Fork)"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[[package]]
name = "pip-licenses"
-version = "3.5.2"
+version = "3.5.3"
description = "Dump the software license list of Python packages installed with pip."
category = "dev"
optional = false
@@ -447,11 +507,11 @@ test = ["docutils", "pytest-cov", "pytest-pycodestyle", "pytest-runner"]
[[package]]
name = "platformdirs"
-version = "2.3.0"
+version = "2.4.1"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.extras]
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
@@ -459,7 +519,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock
[[package]]
name = "pre-commit"
-version = "2.15.0"
+version = "2.16.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
@@ -494,7 +554,7 @@ python-versions = "*"
[[package]]
name = "pycares"
-version = "4.0.0"
+version = "4.1.2"
description = "Python interface for c-ares"
category = "main"
optional = false
@@ -516,7 +576,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pycparser"
-version = "2.20"
+version = "2.21"
description = "C parser in Python"
category = "main"
optional = false
@@ -546,11 +606,22 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pyparsing"
-version = "2.4.7"
+version = "3.0.6"
description = "Python parsing module"
category = "main"
optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+python-versions = ">=3.6"
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "pyreadline3"
+version = "3.3"
+description = "A python implementation of GNU readline."
+category = "main"
+optional = false
+python-versions = "*"
[[package]]
name = "python-dateutil"
@@ -565,11 +636,11 @@ six = ">=1.5"
[[package]]
name = "python-dotenv"
-version = "0.15.0"
-description = "Add .env support to your django/flask apps in development and deployments"
+version = "0.19.2"
+description = "Read key-value pairs from a .env file and set them as environment variables"
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.5"
[package.extras]
cli = ["click (>=5.0)"]
@@ -584,22 +655,28 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[[package]]
name = "rapidfuzz"
-version = "1.5.1"
+version = "1.9.1"
description = "rapid fuzzy string matching"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=2.7"
+
+[package.extras]
+full = ["numpy"]
[[package]]
name = "redis"
-version = "3.5.3"
-description = "Python client for Redis key-value store"
+version = "4.0.2"
+description = "Python client for Redis database and key-value store"
category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.6"
+
+[package.dependencies]
+deprecated = "*"
[package.extras]
-hiredis = ["hiredis (>=0.1.3)"]
+hiredis = ["hiredis (>=1.0.0)"]
[[package]]
name = "sentry-sdk"
@@ -639,7 +716,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "snowballstemmer"
-version = "2.1.0"
+version = "2.2.0"
description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
category = "dev"
optional = false
@@ -654,8 +731,16 @@ optional = false
python-versions = "*"
[[package]]
+name = "soupsieve"
+version = "2.3.1"
+description = "A modern CSS selector implementation for Beautiful Soup."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
+[[package]]
name = "taskipy"
-version = "1.8.1"
+version = "1.9.0"
description = "tasks runner for python projects"
category = "dev"
optional = false
@@ -663,11 +748,24 @@ python-versions = ">=3.6,<4.0"
[package.dependencies]
colorama = ">=0.4.4,<0.5.0"
-mslex = ">=0.3.0,<0.4.0"
+mslex = {version = ">=0.3.0,<0.4.0", markers = "sys_platform == \"win32\""}
psutil = ">=5.7.2,<6.0.0"
toml = ">=0.10.0,<0.11.0"
[[package]]
+name = "testfixtures"
+version = "6.18.3"
+description = "A collection of helpers and mock objects for unit tests and doc tests."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.extras]
+build = ["setuptools-git", "wheel", "twine"]
+docs = ["sphinx", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"]
+test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"]
+
+[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
@@ -677,15 +775,15 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "typing-extensions"
-version = "3.10.0.2"
-description = "Backported and Experimental Type Hints for Python 3.5+"
+version = "4.0.1"
+description = "Backported and Experimental Type Hints for Python 3.6+"
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
[[package]]
name = "urllib3"
-version = "1.26.6"
+version = "1.26.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
@@ -698,26 +796,33 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
-version = "20.7.2"
+version = "20.11.0"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[package.dependencies]
-"backports.entry-points-selectable" = ">=1.0.4"
distlib = ">=0.3.1,<1"
-filelock = ">=3.0.0,<4"
+filelock = ">=3.2,<4"
platformdirs = ">=2,<3"
six = ">=1.9.0,<2"
[package.extras]
-docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
+docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
[[package]]
+name = "wrapt"
+version = "1.13.3"
+description = "Module for decorators, wrappers and monkey patching."
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[[package]]
name = "yarl"
-version = "1.6.3"
+version = "1.7.2"
description = "Yet another URL library"
category = "main"
optional = false
@@ -730,7 +835,7 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "9efbf6be5298ab8ace2588e218be309e105987bfdfa8317453d584a1faac4934"
+content-hash = "27075494e06333e5934e751f9847f419efb712b6d4d4e6173785d47319de1f29"
[metadata.files]
aiodns = [
@@ -793,63 +898,68 @@ async-timeout = [
{file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
]
attrs = [
- {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
- {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
+ {file = "attrs-21.3.0-py2.py3-none-any.whl", hash = "sha256:8f7335278dedd26b58c38e006338242cc0977f06d51579b2b8b87b9b33bff66c"},
+ {file = "attrs-21.3.0.tar.gz", hash = "sha256:50f3c9b216dc9021042f71b392859a773b904ce1a029077f58f6598272432045"},
]
-"backports.entry-points-selectable" = [
- {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"},
- {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"},
+beautifulsoup4 = [
+ {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"},
+ {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"},
]
certifi = [
- {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
- {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
+ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
+ {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
cffi = [
- {file = "cffi-1.14.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c"},
- {file = "cffi-1.14.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99"},
- {file = "cffi-1.14.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819"},
- {file = "cffi-1.14.6-cp27-cp27m-win32.whl", hash = "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20"},
- {file = "cffi-1.14.6-cp27-cp27m-win_amd64.whl", hash = "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224"},
- {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7"},
- {file = "cffi-1.14.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33"},
- {file = "cffi-1.14.6-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534"},
- {file = "cffi-1.14.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a"},
- {file = "cffi-1.14.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5"},
- {file = "cffi-1.14.6-cp35-cp35m-win32.whl", hash = "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca"},
- {file = "cffi-1.14.6-cp35-cp35m-win_amd64.whl", hash = "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218"},
- {file = "cffi-1.14.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b"},
- {file = "cffi-1.14.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb"},
- {file = "cffi-1.14.6-cp36-cp36m-win32.whl", hash = "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a"},
- {file = "cffi-1.14.6-cp36-cp36m-win_amd64.whl", hash = "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e"},
- {file = "cffi-1.14.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c"},
- {file = "cffi-1.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762"},
- {file = "cffi-1.14.6-cp37-cp37m-win32.whl", hash = "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771"},
- {file = "cffi-1.14.6-cp37-cp37m-win_amd64.whl", hash = "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a"},
- {file = "cffi-1.14.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd"},
- {file = "cffi-1.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc"},
- {file = "cffi-1.14.6-cp38-cp38-win32.whl", hash = "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548"},
- {file = "cffi-1.14.6-cp38-cp38-win_amd64.whl", hash = "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156"},
- {file = "cffi-1.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f"},
- {file = "cffi-1.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87"},
- {file = "cffi-1.14.6-cp39-cp39-win32.whl", hash = "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728"},
- {file = "cffi-1.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2"},
- {file = "cffi-1.14.6.tar.gz", hash = "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd"},
+ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
+ {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
+ {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
+ {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
+ {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
+ {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
+ {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
+ {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
+ {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
+ {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
+ {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
+ {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
+ {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
+ {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
+ {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
+ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
+ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
]
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
@@ -863,34 +973,41 @@ colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
-cycler = [
- {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"},
- {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"},
+coloredlogs = [
+ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"},
+ {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"},
+]
+deprecated = [
+ {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
+ {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
]
"discord.py" = []
distlib = [
- {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"},
- {file = "distlib-0.3.2.zip", hash = "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736"},
+ {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
+ {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
+]
+emoji = [
+ {file = "emoji-1.6.3.tar.gz", hash = "sha256:cc28bdc1010b1c03c241f69c7af1e8715144ef45a273bfadc14dc89319ba26d0"},
]
emojis = [
{file = "emojis-0.6.0-py3-none-any.whl", hash = "sha256:7da34c8a78ae262fd68cef9e2c78a3c1feb59784489eeea0f54ba1d4b7111c7c"},
{file = "emojis-0.6.0.tar.gz", hash = "sha256:bf605d1f1a27a81cd37fe82eb65781c904467f569295a541c33710b97e4225ec"},
]
fakeredis = [
- {file = "fakeredis-1.6.0-py3-none-any.whl", hash = "sha256:3449b306f3a85102b28f8180c24722ef966fcb1e3c744758b6f635ec80321a5c"},
- {file = "fakeredis-1.6.0.tar.gz", hash = "sha256:11ccfc9769d718d37e45b382e64a6ba02586b622afa0371a6bd85766d72255f3"},
+ {file = "fakeredis-1.7.0-py3-none-any.whl", hash = "sha256:6f1e04f64557ad3b6835bdc6e5a8d022cbace4bdc24a47ad58f6a72e0fbff760"},
+ {file = "fakeredis-1.7.0.tar.gz", hash = "sha256:c9bd12e430336cbd3e189fae0e91eb99997b93e76dbfdd6ed67fa352dc684c71"},
]
filelock = [
- {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
- {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
+ {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
+ {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
]
flake8 = [
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
]
flake8-annotations = [
- {file = "flake8-annotations-2.6.2.tar.gz", hash = "sha256:0d6cd2e770b5095f09689c9d84cc054c51b929c41a68969ea1beb4b825cac515"},
- {file = "flake8_annotations-2.6.2-py3-none-any.whl", hash = "sha256:d10c4638231f8a50c0a597c4efce42bd7b7d85df4f620a0ddaca526138936a4f"},
+ {file = "flake8-annotations-2.7.0.tar.gz", hash = "sha256:52e53c05b0c06cac1c2dec192ea2c36e85081238add3bd99421d56f574b9479b"},
+ {file = "flake8_annotations-2.7.0-py3-none-any.whl", hash = "sha256:3edfbbfb58e404868834fe6ec3eaf49c139f64f0701259f707d043185545151e"},
]
flake8-bugbear = [
{file = "flake8-bugbear-20.11.1.tar.gz", hash = "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538"},
@@ -900,9 +1017,9 @@ flake8-docstrings = [
{file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"},
{file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"},
]
-flake8-import-order = [
- {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"},
- {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"},
+flake8-isort = [
+ {file = "flake8-isort-4.1.1.tar.gz", hash = "sha256:d814304ab70e6e58859bc5c3e221e2e6e71c958e7005239202fee19c24f82717"},
+ {file = "flake8_isort-4.1.1-py3-none-any.whl", hash = "sha256:c4e8b6dcb7be9b71a02e6e5d4196cefcef0f3447be51e82730fb336fff164949"},
]
flake8-polyfill = [
{file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
@@ -913,8 +1030,8 @@ flake8-string-format = [
{file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"},
]
flake8-tidy-imports = [
- {file = "flake8-tidy-imports-4.4.1.tar.gz", hash = "sha256:c18b3351b998787db071e766e318da1f0bd9d5cecc69c4022a69e7aa2efb2c51"},
- {file = "flake8_tidy_imports-4.4.1-py3-none-any.whl", hash = "sha256:631a1ba9daaedbe8bb53f6086c5a92b390e98371205259e0e311a378df8c3dc8"},
+ {file = "flake8-tidy-imports-4.5.0.tar.gz", hash = "sha256:ac637961d0f319012d099e49619f8c928e3221f74e00fe6eb89513bc64c40adb"},
+ {file = "flake8_tidy_imports-4.5.0-py3-none-any.whl", hash = "sha256:87eed94ae6a2fda6a5918d109746feadf1311e0eb8274ab7a7920f6db00a41c9"},
]
flake8-todo = [
{file = "flake8-todo-0.7.tar.gz", hash = "sha256:6e4c5491ff838c06fe5a771b0e95ee15fc005ca57196011011280fc834a85915"},
@@ -962,82 +1079,83 @@ hiredis = [
{file = "hiredis-2.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0"},
{file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"},
]
+humanfriendly = [
+ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"},
+ {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"},
+]
identify = [
- {file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"},
- {file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"},
+ {file = "identify-2.4.1-py2.py3-none-any.whl", hash = "sha256:0192893ff68b03d37fed553e261d4a22f94ea974093aefb33b29df2ff35fed3c"},
+ {file = "identify-2.4.1.tar.gz", hash = "sha256:64d4885e539f505dd8ffb5e93c142a1db45480452b1594cacd3e91dca9a984e9"},
]
idna = [
- {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
- {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
-]
-kiwisolver = [
- {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1d819553730d3c2724582124aee8a03c846ec4362ded1034c16fb3ef309264e6"},
- {file = "kiwisolver-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d93a1095f83e908fc253f2fb569c2711414c0bfd451cab580466465b235b470"},
- {file = "kiwisolver-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4550a359c5157aaf8507e6820d98682872b9100ce7607f8aa070b4b8af6c298"},
- {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2210f28778c7d2ee13f3c2a20a3a22db889e75f4ec13a21072eabb5693801e84"},
- {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:82f49c5a79d3839bc8f38cb5f4bfc87e15f04cbafa5fbd12fb32c941cb529cfb"},
- {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9661a04ca3c950a8ac8c47f53cbc0b530bce1b52f516a1e87b7736fec24bfff0"},
- {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ddb500a2808c100e72c075cbb00bf32e62763c82b6a882d403f01a119e3f402"},
- {file = "kiwisolver-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72be6ebb4e92520b9726d7146bc9c9b277513a57a38efcf66db0620aec0097e0"},
- {file = "kiwisolver-1.3.2-cp310-cp310-win32.whl", hash = "sha256:83d2c9db5dfc537d0171e32de160461230eb14663299b7e6d18ca6dca21e4977"},
- {file = "kiwisolver-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:cba430db673c29376135e695c6e2501c44c256a81495da849e85d1793ee975ad"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4116ba9a58109ed5e4cb315bdcbff9838f3159d099ba5259c7c7fb77f8537492"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19554bd8d54cf41139f376753af1a644b63c9ca93f8f72009d50a2080f870f77"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a4cf5bbdc861987a7745aed7a536c6405256853c94abc9f3287c3fa401b174"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0007840186bacfaa0aba4466d5890334ea5938e0bb7e28078a0eb0e63b5b59d5"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec2eba188c1906b05b9b49ae55aae4efd8150c61ba450e6721f64620c50b59eb"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3dbb3cea20b4af4f49f84cffaf45dd5f88e8594d18568e0225e6ad9dec0e7967"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-win32.whl", hash = "sha256:5326ddfacbe51abf9469fe668944bc2e399181a2158cb5d45e1d40856b2a0589"},
- {file = "kiwisolver-1.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c6572c2dab23c86a14e82c245473d45b4c515314f1f859e92608dcafbd2f19b8"},
- {file = "kiwisolver-1.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b5074fb09429f2b7bc82b6fb4be8645dcbac14e592128beeff5461dcde0af09f"},
- {file = "kiwisolver-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22521219ca739654a296eea6d4367703558fba16f98688bd8ce65abff36eaa84"},
- {file = "kiwisolver-1.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c358721aebd40c243894298f685a19eb0491a5c3e0b923b9f887ef1193ddf829"},
- {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba5a1041480c6e0a8b11a9544d53562abc2d19220bfa14133e0cdd9967e97af"},
- {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44e6adf67577dbdfa2d9f06db9fbc5639afefdb5bf2b4dfec25c3a7fbc619536"},
- {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d45d1c74f88b9f41062716c727f78f2a59a5476ecbe74956fafb423c5c87a76"},
- {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70adc3658138bc77a36ce769f5f183169bc0a2906a4f61f09673f7181255ac9b"},
- {file = "kiwisolver-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6a5431940f28b6de123de42f0eb47b84a073ee3c3345dc109ad550a3307dd28"},
- {file = "kiwisolver-1.3.2-cp38-cp38-win32.whl", hash = "sha256:ee040a7de8d295dbd261ef2d6d3192f13e2b08ec4a954de34a6fb8ff6422e24c"},
- {file = "kiwisolver-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:8dc3d842fa41a33fe83d9f5c66c0cc1f28756530cd89944b63b072281e852031"},
- {file = "kiwisolver-1.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a498bcd005e8a3fedd0022bb30ee0ad92728154a8798b703f394484452550507"},
- {file = "kiwisolver-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80efd202108c3a4150e042b269f7c78643420cc232a0a771743bb96b742f838f"},
- {file = "kiwisolver-1.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f8eb7b6716f5b50e9c06207a14172cf2de201e41912ebe732846c02c830455b9"},
- {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f441422bb313ab25de7b3dbfd388e790eceb76ce01a18199ec4944b369017009"},
- {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:30fa008c172355c7768159983a7270cb23838c4d7db73d6c0f6b60dde0d432c6"},
- {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f8f6c8f4f1cff93ca5058d6ec5f0efda922ecb3f4c5fb76181f327decff98b8"},
- {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba677bcaff9429fd1bf01648ad0901cea56c0d068df383d5f5856d88221fe75b"},
- {file = "kiwisolver-1.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7843b1624d6ccca403a610d1277f7c28ad184c5aa88a1750c1a999754e65b439"},
- {file = "kiwisolver-1.3.2-cp39-cp39-win32.whl", hash = "sha256:e6f5eb2f53fac7d408a45fbcdeda7224b1cfff64919d0f95473420a931347ae9"},
- {file = "kiwisolver-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:eedd3b59190885d1ebdf6c5e0ca56828beb1949b4dfe6e5d0256a461429ac386"},
- {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dedc71c8eb9c5096037766390172c34fb86ef048b8e8958b4e484b9e505d66bc"},
- {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bf7eb45d14fc036514c09554bf983f2a72323254912ed0c3c8e697b62c4c158f"},
- {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b65bd35f3e06a47b5c30ea99e0c2b88f72c6476eedaf8cfbc8e66adb5479dcf"},
- {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25405f88a37c5f5bcba01c6e350086d65e7465fd1caaf986333d2a045045a223"},
- {file = "kiwisolver-1.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bcadb05c3d4794eb9eee1dddf1c24215c92fb7b55a80beae7a60530a91060560"},
- {file = "kiwisolver-1.3.2.tar.gz", hash = "sha256:fc4453705b81d03568d5b808ad8f09c77c47534f6ac2e72e733f9ca4714aa75c"},
-]
-matplotlib = [
- {file = "matplotlib-3.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c988bb43414c7c2b0a31bd5187b4d27fd625c080371b463a6d422047df78913"},
- {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1c5efc278d996af8a251b2ce0b07bbeccb821f25c8c9846bdcb00ffc7f158aa"},
- {file = "matplotlib-3.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:eeb1859efe7754b1460e1d4991bbd4a60a56f366bc422ef3a9c5ae05f0bc70b5"},
- {file = "matplotlib-3.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:844a7b0233e4ff7fba57e90b8799edaa40b9e31e300b8d5efc350937fa8b1bea"},
- {file = "matplotlib-3.4.3-cp37-cp37m-win32.whl", hash = "sha256:85f0c9cf724715e75243a7b3087cf4a3de056b55e05d4d76cc58d610d62894f3"},
- {file = "matplotlib-3.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c70b6311dda3e27672f1bf48851a0de816d1ca6aaf3d49365fbdd8e959b33d2b"},
- {file = "matplotlib-3.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b884715a59fec9ad3b6048ecf3860f3b2ce965e676ef52593d6fa29abcf7d330"},
- {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a78a3b51f29448c7f4d4575e561f6b0dbb8d01c13c2046ab6c5220eb25c06506"},
- {file = "matplotlib-3.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a724e3a48a54b8b6e7c4ae38cd3d07084508fa47c410c8757e9db9791421838"},
- {file = "matplotlib-3.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:48e1e0859b54d5f2e29bb78ca179fd59b971c6ceb29977fb52735bfd280eb0f5"},
- {file = "matplotlib-3.4.3-cp38-cp38-win32.whl", hash = "sha256:01c9de93a2ca0d128c9064f23709362e7fefb34910c7c9e0b8ab0de8258d5eda"},
- {file = "matplotlib-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:ebfb01a65c3f5d53a8c2a8133fec2b5221281c053d944ae81ff5822a68266617"},
- {file = "matplotlib-3.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b53f336a4688cfce615887505d7e41fd79b3594bf21dd300531a4f5b4f746a"},
- {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:fcd6f1954943c0c192bfbebbac263f839d7055409f1173f80d8b11a224d236da"},
- {file = "matplotlib-3.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6be8df61b1626e1a142c57e065405e869e9429b4a6dab4a324757d0dc4d42235"},
- {file = "matplotlib-3.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:41b6e307458988891fcdea2d8ecf84a8c92d53f84190aa32da65f9505546e684"},
- {file = "matplotlib-3.4.3-cp39-cp39-win32.whl", hash = "sha256:f72657f1596199dc1e4e7a10f52a4784ead8a711f4e5b59bea95bdb97cf0e4fd"},
- {file = "matplotlib-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:f15edcb0629a0801738925fe27070480f446fcaa15de65946ff946ad99a59a40"},
- {file = "matplotlib-3.4.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:556965514b259204637c360d213de28d43a1f4aed1eca15596ce83f768c5a56f"},
- {file = "matplotlib-3.4.3-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:54a026055d5f8614f184e588f6e29064019a0aa8448450214c0b60926d62d919"},
- {file = "matplotlib-3.4.3.tar.gz", hash = "sha256:fc4f526dfdb31c9bd6b8ca06bf9fab663ca12f3ec9cdf4496fb44bc680140318"},
+ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
+ {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+]
+isort = [
+ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
+ {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
+]
+lxml = [
+ {file = "lxml-4.7.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f"},
+ {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e"},
+ {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17"},
+ {file = "lxml-4.7.1-cp27-cp27m-win32.whl", hash = "sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419"},
+ {file = "lxml-4.7.1-cp27-cp27m-win_amd64.whl", hash = "sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2"},
+ {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5"},
+ {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d"},
+ {file = "lxml-4.7.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc"},
+ {file = "lxml-4.7.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e"},
+ {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3"},
+ {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175"},
+ {file = "lxml-4.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6"},
+ {file = "lxml-4.7.1-cp310-cp310-win32.whl", hash = "sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6"},
+ {file = "lxml-4.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e"},
+ {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675"},
+ {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7"},
+ {file = "lxml-4.7.1-cp35-cp35m-win32.whl", hash = "sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5"},
+ {file = "lxml-4.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03"},
+ {file = "lxml-4.7.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b"},
+ {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55"},
+ {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d"},
+ {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d"},
+ {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6"},
+ {file = "lxml-4.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d"},
+ {file = "lxml-4.7.1-cp36-cp36m-win32.whl", hash = "sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d"},
+ {file = "lxml-4.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a"},
+ {file = "lxml-4.7.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8"},
+ {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868"},
+ {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a"},
+ {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986"},
+ {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3"},
+ {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944"},
+ {file = "lxml-4.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b"},
+ {file = "lxml-4.7.1-cp37-cp37m-win32.whl", hash = "sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4"},
+ {file = "lxml-4.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1"},
+ {file = "lxml-4.7.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"},
+ {file = "lxml-4.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e"},
+ {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440"},
+ {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f"},
+ {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e"},
+ {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e"},
+ {file = "lxml-4.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9"},
+ {file = "lxml-4.7.1-cp38-cp38-win32.whl", hash = "sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd"},
+ {file = "lxml-4.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d"},
+ {file = "lxml-4.7.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9"},
+ {file = "lxml-4.7.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489"},
+ {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36"},
+ {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42"},
+ {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db"},
+ {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851"},
+ {file = "lxml-4.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4"},
+ {file = "lxml-4.7.1-cp39-cp39-win32.whl", hash = "sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0"},
+ {file = "lxml-4.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2"},
+ {file = "lxml-4.7.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4"},
+ {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f"},
+ {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a"},
+ {file = "lxml-4.7.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7"},
+ {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267"},
+ {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60"},
+ {file = "lxml-4.7.1.tar.gz", hash = "sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24"},
]
mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
@@ -1048,148 +1166,139 @@ mslex = [
{file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"},
]
multidict = [
- {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"},
- {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"},
- {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"},
- {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"},
- {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"},
- {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"},
- {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"},
- {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"},
- {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"},
- {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"},
- {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"},
- {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"},
- {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"},
- {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"},
- {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"},
- {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"},
- {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"},
+ {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"},
+ {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"},
+ {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"},
+ {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"},
+ {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"},
+ {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"},
+ {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"},
+ {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"},
+ {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"},
+ {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"},
+ {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"},
+ {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"},
+ {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"},
+ {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"},
+ {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"},
+ {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"},
+ {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"},
+ {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"},
+ {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"},
+ {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"},
+ {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"},
+ {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"},
+ {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"},
+ {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"},
+ {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"},
+ {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"},
+ {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"},
+ {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"},
+ {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"},
+ {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"},
+ {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"},
+ {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"},
]
nodeenv = [
{file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"},
{file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
]
-numpy = [
- {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"},
- {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"},
- {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"},
- {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"},
- {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"},
- {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"},
- {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"},
- {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"},
- {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"},
- {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"},
- {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"},
- {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"},
- {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"},
- {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"},
- {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"},
- {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"},
- {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"},
- {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"},
- {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"},
- {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"},
- {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"},
- {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"},
- {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"},
- {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"},
- {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"},
- {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"},
- {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"},
- {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"},
+packaging = [
+ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
+ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pep8-naming = [
{file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"},
{file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"},
]
pillow = [
- {file = "Pillow-8.3.2-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:c691b26283c3a31594683217d746f1dad59a7ae1d4cfc24626d7a064a11197d4"},
- {file = "Pillow-8.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f514c2717012859ccb349c97862568fdc0479aad85b0270d6b5a6509dbc142e2"},
- {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be25cb93442c6d2f8702c599b51184bd3ccd83adebd08886b682173e09ef0c3f"},
- {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d675a876b295afa114ca8bf42d7f86b5fb1298e1b6bb9a24405a3f6c8338811c"},
- {file = "Pillow-8.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59697568a0455764a094585b2551fd76bfd6b959c9f92d4bdec9d0e14616303a"},
- {file = "Pillow-8.3.2-cp310-cp310-win32.whl", hash = "sha256:2d5e9dc0bf1b5d9048a94c48d0813b6c96fccfa4ccf276d9c36308840f40c228"},
- {file = "Pillow-8.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:11c27e74bab423eb3c9232d97553111cc0be81b74b47165f07ebfdd29d825875"},
- {file = "Pillow-8.3.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:11eb7f98165d56042545c9e6db3ce394ed8b45089a67124298f0473b29cb60b2"},
- {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f23b2d3079522fdf3c09de6517f625f7a964f916c956527bed805ac043799b8"},
- {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19ec4cfe4b961edc249b0e04b5618666c23a83bc35842dea2bfd5dfa0157f81b"},
- {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a31c07cea5edbaeb4bdba6f2b87db7d3dc0f446f379d907e51cc70ea375629"},
- {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15ccb81a6ffc57ea0137f9f3ac2737ffa1d11f786244d719639df17476d399a7"},
- {file = "Pillow-8.3.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8f284dc1695caf71a74f24993b7c7473d77bc760be45f776a2c2f4e04c170550"},
- {file = "Pillow-8.3.2-cp36-cp36m-win32.whl", hash = "sha256:4abc247b31a98f29e5224f2d31ef15f86a71f79c7f4d2ac345a5d551d6393073"},
- {file = "Pillow-8.3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a048dad5ed6ad1fad338c02c609b862dfaa921fcd065d747194a6805f91f2196"},
- {file = "Pillow-8.3.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:06d1adaa284696785375fa80a6a8eb309be722cf4ef8949518beb34487a3df71"},
- {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd24054aaf21e70a51e2a2a5ed1183560d3a69e6f9594a4bfe360a46f94eba83"},
- {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a330bf7014ee034046db43ccbb05c766aa9e70b8d6c5260bfc38d73103b0ba"},
- {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13654b521fb98abdecec105ea3fb5ba863d1548c9b58831dd5105bb3873569f1"},
- {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1bd983c565f92779be456ece2479840ec39d386007cd4ae83382646293d681b"},
- {file = "Pillow-8.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4326ea1e2722f3dc00ed77c36d3b5354b8fb7399fb59230249ea6d59cbed90da"},
- {file = "Pillow-8.3.2-cp37-cp37m-win32.whl", hash = "sha256:085a90a99404b859a4b6c3daa42afde17cb3ad3115e44a75f0d7b4a32f06a6c9"},
- {file = "Pillow-8.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:18a07a683805d32826c09acfce44a90bf474e6a66ce482b1c7fcd3757d588df3"},
- {file = "Pillow-8.3.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4e59e99fd680e2b8b11bbd463f3c9450ab799305d5f2bafb74fefba6ac058616"},
- {file = "Pillow-8.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d89a2e9219a526401015153c0e9dd48319ea6ab9fe3b066a20aa9aee23d9fd3"},
- {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56fd98c8294f57636084f4b076b75f86c57b2a63a8410c0cd172bc93695ee979"},
- {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b11c9d310a3522b0fd3c35667914271f570576a0e387701f370eb39d45f08a4"},
- {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0412516dcc9de9b0a1e0ae25a280015809de8270f134cc2c1e32c4eeb397cf30"},
- {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bcb04ff12e79b28be6c9988f275e7ab69f01cc2ba319fb3114f87817bb7c74b6"},
- {file = "Pillow-8.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b9911ec70731711c3b6ebcde26caea620cbdd9dcb73c67b0730c8817f24711b"},
- {file = "Pillow-8.3.2-cp38-cp38-win32.whl", hash = "sha256:ce2e5e04bb86da6187f96d7bab3f93a7877830981b37f0287dd6479e27a10341"},
- {file = "Pillow-8.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:35d27687f027ad25a8d0ef45dd5208ef044c588003cdcedf05afb00dbc5c2deb"},
- {file = "Pillow-8.3.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:04835e68ef12904bc3e1fd002b33eea0779320d4346082bd5b24bec12ad9c3e9"},
- {file = "Pillow-8.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10e00f7336780ca7d3653cf3ac26f068fa11b5a96894ea29a64d3dc4b810d630"},
- {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cde7a4d3687f21cffdf5bb171172070bb95e02af448c4c8b2f223d783214056"},
- {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c3ff00110835bdda2b1e2b07f4a2548a39744bb7de5946dc8e95517c4fb2ca6"},
- {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d409030bf3bd05fa66fb5fdedc39c521b397f61ad04309c90444e893d05f7d"},
- {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bff50ba9891be0a004ef48828e012babaaf7da204d81ab9be37480b9020a82b"},
- {file = "Pillow-8.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7dbfbc0020aa1d9bc1b0b8bcf255a7d73f4ad0336f8fd2533fcc54a4ccfb9441"},
- {file = "Pillow-8.3.2-cp39-cp39-win32.whl", hash = "sha256:963ebdc5365d748185fdb06daf2ac758116deecb2277ec5ae98139f93844bc09"},
- {file = "Pillow-8.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:cc9d0dec711c914ed500f1d0d3822868760954dce98dfb0b7382a854aee55d19"},
- {file = "Pillow-8.3.2-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2c661542c6f71dfd9dc82d9d29a8386287e82813b0375b3a02983feac69ef864"},
- {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:548794f99ff52a73a156771a0402f5e1c35285bd981046a502d7e4793e8facaa"},
- {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b68f565a4175e12e68ca900af8910e8fe48aaa48fd3ca853494f384e11c8bcd"},
- {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:838eb85de6d9307c19c655c726f8d13b8b646f144ca6b3771fa62b711ebf7624"},
- {file = "Pillow-8.3.2-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:feb5db446e96bfecfec078b943cc07744cc759893cef045aa8b8b6d6aaa8274e"},
- {file = "Pillow-8.3.2-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:fc0db32f7223b094964e71729c0361f93db43664dd1ec86d3df217853cedda87"},
- {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd4fd83aa912d7b89b4b4a1580d30e2a4242f3936882a3f433586e5ab97ed0d5"},
- {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d0c8ebbfd439c37624db98f3877d9ed12c137cadd99dde2d2eae0dab0bbfc355"},
- {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cb3dd7f23b044b0737317f892d399f9e2f0b3a02b22b2c692851fb8120d82c6"},
- {file = "Pillow-8.3.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a66566f8a22561fc1a88dc87606c69b84fa9ce724f99522cf922c801ec68f5c1"},
- {file = "Pillow-8.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ce651ca46d0202c302a535d3047c55a0131a720cf554a578fc1b8a2aff0e7d96"},
- {file = "Pillow-8.3.2.tar.gz", hash = "sha256:dde3f3ed8d00c72631bc19cbfff8ad3b6215062a5eed402381ad365f82f0c18c"},
+ {file = "Pillow-9.0.1-1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5d24e1d674dd9d72c66ad3ea9131322819ff86250b30dc5821cbafcfa0b96b4"},
+ {file = "Pillow-9.0.1-1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2632d0f846b7c7600edf53c48f8f9f1e13e62f66a6dbc15191029d950bfed976"},
+ {file = "Pillow-9.0.1-1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9618823bd237c0d2575283f2939655f54d51b4527ec3972907a927acbcc5bfc"},
+ {file = "Pillow-9.0.1-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:9bfdb82cdfeccec50aad441afc332faf8606dfa5e8efd18a6692b5d6e79f00fd"},
+ {file = "Pillow-9.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5100b45a4638e3c00e4d2320d3193bdabb2d75e79793af7c3eb139e4f569f16f"},
+ {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:528a2a692c65dd5cafc130de286030af251d2ee0483a5bf50c9348aefe834e8a"},
+ {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f29d831e2151e0b7b39981756d201f7108d3d215896212ffe2e992d06bfe049"},
+ {file = "Pillow-9.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:855c583f268edde09474b081e3ddcd5cf3b20c12f26e0d434e1386cc5d318e7a"},
+ {file = "Pillow-9.0.1-cp310-cp310-win32.whl", hash = "sha256:d9d7942b624b04b895cb95af03a23407f17646815495ce4547f0e60e0b06f58e"},
+ {file = "Pillow-9.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:81c4b81611e3a3cb30e59b0cf05b888c675f97e3adb2c8672c3154047980726b"},
+ {file = "Pillow-9.0.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:413ce0bbf9fc6278b2d63309dfeefe452835e1c78398efb431bab0672fe9274e"},
+ {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80fe64a6deb6fcfdf7b8386f2cf216d329be6f2781f7d90304351811fb591360"},
+ {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cef9c85ccbe9bee00909758936ea841ef12035296c748aaceee535969e27d31b"},
+ {file = "Pillow-9.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d19397351f73a88904ad1aee421e800fe4bbcd1aeee6435fb62d0a05ccd1030"},
+ {file = "Pillow-9.0.1-cp37-cp37m-win32.whl", hash = "sha256:d21237d0cd37acded35154e29aec853e945950321dd2ffd1a7d86fe686814669"},
+ {file = "Pillow-9.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ede5af4a2702444a832a800b8eb7f0a7a1c0eed55b644642e049c98d589e5092"},
+ {file = "Pillow-9.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b5b3f092fe345c03bca1e0b687dfbb39364b21ebb8ba90e3fa707374b7915204"},
+ {file = "Pillow-9.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:335ace1a22325395c4ea88e00ba3dc89ca029bd66bd5a3c382d53e44f0ccd77e"},
+ {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db6d9fac65bd08cea7f3540b899977c6dee9edad959fa4eaf305940d9cbd861c"},
+ {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f154d173286a5d1863637a7dcd8c3437bb557520b01bddb0be0258dcb72696b5"},
+ {file = "Pillow-9.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d4b1341ac07ae07eb2cc682f459bec932a380c3b122f5540432d8977e64eae"},
+ {file = "Pillow-9.0.1-cp38-cp38-win32.whl", hash = "sha256:effb7749713d5317478bb3acb3f81d9d7c7f86726d41c1facca068a04cf5bb4c"},
+ {file = "Pillow-9.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:7f7609a718b177bf171ac93cea9fd2ddc0e03e84d8fa4e887bdfc39671d46b00"},
+ {file = "Pillow-9.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:80ca33961ced9c63358056bd08403ff866512038883e74f3a4bf88ad3eb66838"},
+ {file = "Pillow-9.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c3c33ac69cf059bbb9d1a71eeaba76781b450bc307e2291f8a4764d779a6b28"},
+ {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12875d118f21cf35604176872447cdb57b07126750a33748bac15e77f90f1f9c"},
+ {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:514ceac913076feefbeaf89771fd6febde78b0c4c1b23aaeab082c41c694e81b"},
+ {file = "Pillow-9.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c5c79ab7dfce6d88f1ba639b77e77a17ea33a01b07b99840d6ed08031cb2a7"},
+ {file = "Pillow-9.0.1-cp39-cp39-win32.whl", hash = "sha256:718856856ba31f14f13ba885ff13874be7fefc53984d2832458f12c38205f7f7"},
+ {file = "Pillow-9.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:f25ed6e28ddf50de7e7ea99d7a976d6a9c415f03adcaac9c41ff6ff41b6d86ac"},
+ {file = "Pillow-9.0.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:011233e0c42a4a7836498e98c1acf5e744c96a67dd5032a6f666cc1fb97eab97"},
+ {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253e8a302a96df6927310a9d44e6103055e8fb96a6822f8b7f514bb7ef77de56"},
+ {file = "Pillow-9.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6295f6763749b89c994fcb6d8a7f7ce03c3992e695f89f00b741b4580b199b7e"},
+ {file = "Pillow-9.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a9f44cd7e162ac6191491d7249cceb02b8116b0f7e847ee33f739d7cb1ea1f70"},
+ {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
]
pip-licenses = [
- {file = "pip-licenses-3.5.2.tar.gz", hash = "sha256:c5e984f461b34ad04dafa151d0048eb9d049e3d6439966c6440bb6b53ad077b6"},
- {file = "pip_licenses-3.5.2-py3-none-any.whl", hash = "sha256:62deafc82d5dccea1a4cab55172706e02f228abcd67f4d53e382fcb1497e9b62"},
+ {file = "pip-licenses-3.5.3.tar.gz", hash = "sha256:f44860e00957b791c6c6005a3328f2d5eaeee96ddb8e7d87d4b0aa25b02252e4"},
+ {file = "pip_licenses-3.5.3-py3-none-any.whl", hash = "sha256:59c148d6a03784bf945d232c0dc0e9de4272a3675acaa0361ad7712398ca86ba"},
]
platformdirs = [
- {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"},
- {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"},
+ {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
+ {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
]
pre-commit = [
- {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"},
- {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"},
+ {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"},
+ {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"},
]
psutil = [
{file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"},
@@ -1225,47 +1334,45 @@ ptable = [
{file = "PTable-0.9.2.tar.gz", hash = "sha256:aa7fc151cb40f2dabcd2275ba6f7fd0ff8577a86be3365cd3fb297cbe09cc292"},
]
pycares = [
- {file = "pycares-4.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db5a533111a3cfd481e7e4fb2bf8bef69f4fa100339803e0504dd5aecafb96a5"},
- {file = "pycares-4.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fdff88393c25016f417770d82678423fc7a56995abb2df3d2a1e55725db6977d"},
- {file = "pycares-4.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0aa97f900a7ffb259be77d640006585e2a907b0cd4edeee0e85cf16605995d5a"},
- {file = "pycares-4.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:a34b0e3e693dceb60b8a1169668d606c75cb100ceba0a2df53c234a0eb067fbc"},
- {file = "pycares-4.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7661d6bbd51a337e7373cb356efa8be9b4655fda484e068f9455e939aec8d54e"},
- {file = "pycares-4.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:57315b8eb8fdbc56b3ad4932bc4b17132bb7c7fd2bd590f7fb84b6b522098aa9"},
- {file = "pycares-4.0.0-cp36-cp36m-win32.whl", hash = "sha256:dca9dc58845a9d083f302732a3130c68ded845ad5d463865d464e53c75a3dd45"},
- {file = "pycares-4.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c95c964d5dd307e104b44b193095c67bb6b10c9eda1ffe7d44ab7a9e84c476d9"},
- {file = "pycares-4.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26e67e4f81c80a5955dcf6193f3d9bee3c491fc0056299b383b84d792252fba4"},
- {file = "pycares-4.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cd3011ffd5e1ad55880f7256791dbab9c43ebeda260474a968f19cd0319e1aef"},
- {file = "pycares-4.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1b959dd5921d207d759d421eece1b60416df33a7f862465739d5f2c363c2f523"},
- {file = "pycares-4.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6f258c1b74c048a9501a25f732f11b401564005e5e3c18f1ca6cad0c3dc0fb19"},
- {file = "pycares-4.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:b17ef48729786e62b574c6431f675f4cb02b27691b49e7428a605a50cd59c072"},
- {file = "pycares-4.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:82b3259cb590ddd107a6d2dc52da2a2e9a986bf242e893d58c786af2f8191047"},
- {file = "pycares-4.0.0-cp37-cp37m-win32.whl", hash = "sha256:4876fc790ae32832ae270c4a010a1a77e12ddf8d8e6ad70ad0b0a9d506c985f7"},
- {file = "pycares-4.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f60c04c5561b1ddf85ca4e626943cc09d7fb684e1adb22abb632095415a40fd7"},
- {file = "pycares-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:615406013cdcd1b445e5d1a551d276c6200b3abe77e534f8a7f7e1551208d14f"},
- {file = "pycares-4.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6580aef5d1b29a88c3d72fe73c691eacfd454f86e74d3fdd18f4bad8e8def98b"},
- {file = "pycares-4.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8ebb3ba0485f66cae8eed7ce3e9ed6f2c0bfd5e7319d5d0fbbb511064f17e1d4"},
- {file = "pycares-4.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c5362b7690ca481440f6b98395ac6df06aa50518ccb183c560464d1e5e2ab5d4"},
- {file = "pycares-4.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:eb60be66accc9a9ea1018b591a1f5800cba83491d07e9acc8c56bc6e6607ab54"},
- {file = "pycares-4.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:44896d6e191a6b5a914dbe3aa7c748481bf6ad19a9df33c1e76f8f2dc33fc8f0"},
- {file = "pycares-4.0.0-cp38-cp38-win32.whl", hash = "sha256:09b28fc7bc2cc05f7f69bf1636ddf46086e0a1837b62961e2092fcb40477320d"},
- {file = "pycares-4.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4a5081e232c1d181883dcac4675807f3a6cf33911c4173fbea00c0523687ed4"},
- {file = "pycares-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:103353577a6266a53e71bfee4cf83825f1401fefa60f0fb8bdec35f13be6a5f2"},
- {file = "pycares-4.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ad6caf580ee69806fc6534be93ddbb6e99bf94296d79ab351c37b2992b17abfd"},
- {file = "pycares-4.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3d5e50c95849f6905d2a9dbf02ed03f82580173e3c5604a39e2ad054185631f1"},
- {file = "pycares-4.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:53bc4f181b19576499b02cea4b45391e8dcbe30abd4cd01492f66bfc15615a13"},
- {file = "pycares-4.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:d52f9c725d2a826d5ffa37681eb07ffb996bfe21788590ef257664a3898fc0b5"},
- {file = "pycares-4.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3c7fb8d34ee11971c39acfaf98d0fac66725385ccef3bfe1b174c92b210e1aa4"},
- {file = "pycares-4.0.0-cp39-cp39-win32.whl", hash = "sha256:e9773e07684a55f54657df05237267611a77b294ec3bacb5f851c4ffca38a465"},
- {file = "pycares-4.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:38e54037f36c149146ff15f17a4a963fbdd0f9871d4a21cd94ff9f368140f57e"},
- {file = "pycares-4.0.0.tar.gz", hash = "sha256:d0154fc5753b088758fbec9bc137e1b24bb84fc0c6a09725c8bac25a342311cd"},
+ {file = "pycares-4.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:71b99b9e041ae3356b859822c511f286f84c8889ec9ed1fbf6ac30fb4da13e4c"},
+ {file = "pycares-4.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c000942f5fc64e6e046aa61aa53b629b576ba11607d108909727c3c8f211a157"},
+ {file = "pycares-4.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b0e50ddc78252f2e2b6b5f2c73e5b2449dfb6bea7a5a0e21dfd1e2bcc9e17382"},
+ {file = "pycares-4.1.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6831e963a910b0a8cbdd2750ffcdf5f2bb0edb3f53ca69ff18484de2cc3807c4"},
+ {file = "pycares-4.1.2-cp310-cp310-win32.whl", hash = "sha256:ad7b28e1b6bc68edd3d678373fa3af84e39d287090434f25055d21b4716b2fc6"},
+ {file = "pycares-4.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:27a6f09dbfb69bb79609724c0f90dfaa7c215876a7cd9f12d585574d1f922112"},
+ {file = "pycares-4.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e5a060f5fa90ae245aa99a4a8ad13ec39c2340400de037c7e8d27b081e1a3c64"},
+ {file = "pycares-4.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:056330275dea42b7199494047a745e1d9785d39fb8c4cd469dca043532240b80"},
+ {file = "pycares-4.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0aa897543a786daba74ec5e19638bd38b2b432d179a0e248eac1e62de5756207"},
+ {file = "pycares-4.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cbceaa9b2c416aa931627466d3240aecfc905c292c842252e3d77b8630072505"},
+ {file = "pycares-4.1.2-cp36-cp36m-win32.whl", hash = "sha256:112e1385c451069112d6b5ea1f9c378544f3c6b89882ff964e9a64be3336d7e4"},
+ {file = "pycares-4.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:c6680f7fdc0f1163e8f6c2a11d11b9a0b524a61000d2a71f9ccd410f154fb171"},
+ {file = "pycares-4.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a41a2baabcd95266db776c510d349d417919407f03510fc87ac7488730d913"},
+ {file = "pycares-4.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a810d01c9a426ee8b0f36969c2aef5fb966712be9d7e466920beb328cd9cefa3"},
+ {file = "pycares-4.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b266cec81dcea2c3efbbd3dda00af8d7eb0693ae9e47e8706518334b21f27d4a"},
+ {file = "pycares-4.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8319afe4838e09df267c421ca93da408f770b945ec6217dda72f1f6a493e37e4"},
+ {file = "pycares-4.1.2-cp37-cp37m-win32.whl", hash = "sha256:4d5da840aa0d9b15fa51107f09270c563a348cb77b14ae9653d0bbdbe326fcc2"},
+ {file = "pycares-4.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5632f21d92cc0225ba5ff906e4e5dec415ef0b3df322c461d138190681cd5d89"},
+ {file = "pycares-4.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8fd1ff17a26bb004f0f6bb902ba7dddd810059096ae0cc3b45e4f5be46315d19"},
+ {file = "pycares-4.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439799be4b7576e907139a7f9b3c8a01b90d3e38af4af9cd1fc6c1ee9a42b9e6"},
+ {file = "pycares-4.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:40079ed58efa91747c50aac4edf8ecc7e570132ab57dc0a4030eb0d016a6cab8"},
+ {file = "pycares-4.1.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e190471a015f8225fa38069617192e06122771cce2b169ac7a60bfdbd3d4ab2"},
+ {file = "pycares-4.1.2-cp38-cp38-win32.whl", hash = "sha256:2b837315ed08c7df009b67725fe1f50489e99de9089f58ec1b243dc612f172aa"},
+ {file = "pycares-4.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:c7eba3c8354b730a54d23237d0b6445a2f68570fa68d0848887da23a3f3b71f3"},
+ {file = "pycares-4.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2f5f84fe9f83eab9cd68544b165b74ba6e3412d029cc9ab20098d9c332869fc5"},
+ {file = "pycares-4.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569eef8597b5e02b1bc4644b9f272160304d8c9985357d7ecfcd054da97c0771"},
+ {file = "pycares-4.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e1489aa25d14dbf7176110ead937c01176ed5a0ebefd3b092bbd6b202241814c"},
+ {file = "pycares-4.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dc942692fca0e27081b7bb414bb971d34609c80df5e953f6d0c62ecc8019acd9"},
+ {file = "pycares-4.1.2-cp39-cp39-win32.whl", hash = "sha256:ed71dc4290d9c3353945965604ef1f6a4de631733e9819a7ebc747220b27e641"},
+ {file = "pycares-4.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:ec00f3594ee775665167b1a1630edceefb1b1283af9ac57480dba2fb6fd6c360"},
+ {file = "pycares-4.1.2.tar.gz", hash = "sha256:03490be0e7b51a0c8073f877bec347eff31003f64f57d9518d419d9369452837"},
]
pycodestyle = [
{file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
{file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
]
pycparser = [
- {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
- {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pydocstyle = [
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
@@ -1276,16 +1383,20 @@ pyflakes = [
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
]
pyparsing = [
- {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
- {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+ {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"},
+ {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"},
+]
+pyreadline3 = [
+ {file = "pyreadline3-3.3-py3-none-any.whl", hash = "sha256:0003fd0079d152ecbd8111202c5a7dfa6a5569ffd65b235e45f3c2ecbee337b4"},
+ {file = "pyreadline3-3.3.tar.gz", hash = "sha256:ff3b5a1ac0010d0967869f723e687d42cabc7dccf33b14934c92aa5168d260b3"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
]
python-dotenv = [
- {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"},
- {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"},
+ {file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
+ {file = "python_dotenv-0.19.2-py2.py3-none-any.whl", hash = "sha256:32b2bdc1873fd3a3c346da1c6db83d0053c3c62f28f1f38516070c4c8971b1d3"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
@@ -1319,71 +1430,62 @@ pyyaml = [
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
]
rapidfuzz = [
- {file = "rapidfuzz-1.5.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:6a951ad31ef121bacf40bbe6fbd387740d5038400ec2bfb41d037e9fd2754ef5"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:b64acce4f6e745417218fc0bb6ff31b26fac0d723506b82ee4b9cad448b85ebb"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a834061e6d4dfb9672e89e28583486f60821796cf0d7cc559643a0d597ce33a9"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:50548b919bc7608f7b9b4780415ddad135cfc3a54135bdb4bd0bb7ff2cdf9fdf"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:c39c7f200eef49f4f9d6b808950709334e6f1c22262d570f1f77d6d3d373ad81"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:347973ddf12d66d4d06daf1aca3a096a1bffe12306bcf13b832bdfc8db6d9f4a"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:e6cd6717d87d02dde2088c080b0851bdba970b77085b68e213a7b786dee4be88"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:ba956add4c34da019fb5e8f5e1768604b05569dd68055382795ad9062b9ca55e"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-win32.whl", hash = "sha256:95164076d8e0433f9f93e218270f19e3020a3a9b8db28a3d74143810d4243600"},
- {file = "rapidfuzz-1.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:274878c59440d6ad3efca833da61594836306af7dcdd914cc1b6ceb2d4cea23b"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3b3df0c1a307a64273f6fd64c0f28218e002768eda1d94b9fffdab9371e38a6a"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:55472932a8bcf008855b2cc8e5bc47d60066b504ef02dbf8d8fd43ddd8f20a6e"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:170e71f2ec36a086ce5d2667331721cc9b779370d0ef7248ef6979819cd8fb09"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:00734857b412afc35b29f0ea2f1d9ee26ff93d4cd3fa5f47bb90f6aef385f2a1"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ef0cf038c910a3ed626a3224effde8eb49dd7dcda87af59fcd37bc63b78a9bd1"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9f454f79bc463e3de08c5d5c0f438fce1b1736cd4da1a1f47f72dc37da156552"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:05e8dede642af1b38ebcf8fb5e9bbfdcdf8debba660ae1aafb5c1b0e6ca3e4de"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:f08773adb7f21e1f530bad2c6ababaf472f80283650bc265a7e8f614480cd49c"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-win32.whl", hash = "sha256:4e1a7fb18d4a6c3d471a3ad8f820f179216de61bef74663342340cf9c685a31e"},
- {file = "rapidfuzz-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:09e992579c4aae59310e44db99ed848a8437ed1e8810a513d3bbab7ac7a8f215"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e2cd05869bd50f25b6d384e0cc73f8cfd6ebb8f1e7bdf1315384e21611f091"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b173f8d4c9360b8b32b5ab7a669623f239cb85013e1749bdca03e1b3c297faa7"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6ab75111e2216a48c7e01d47d8903fc2d0c1df398e7262a6df544d86812e40c7"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:eb560622d9970eb0c615d5dff26af8a8647ba541a89a927fca1eb0862898f997"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:59246615b819e4aff685aa57359f5bbaf02441cccc83e8899608037542a3fe36"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:7a960dfedcf1acdb8435b5b00aebfc2ee8fd53b7b4f7acf613915b4c24fc0ef7"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:3d727a7e09f1a01b61452c86d687d0564bad923d5d209224549ae790408d6449"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:eb5e43dbef900367b91fb73a4c447efde034656b25b397844c8cf622dae84ac3"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-win32.whl", hash = "sha256:03ea226734cca3f86bc402fc04b8a38b795795e99dbf02dd834845b80bcf7588"},
- {file = "rapidfuzz-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5e9beeb6643d663c410ad8ccf88eafbe59ba7aa9b34eea5b51c6312976789803"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0d3cdb6ced024ed1567ba0be4b0909b17f691bd6e9e9f29626e4953ecf7cba9e"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c06f98bb94fbad9b773c38a3e2cf28a315466b41f862917ba4d228052bcc0966"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7bf51eb2ff342c4a0d77ab22b3d7de461ef9d2c480fd863c57fb139e7578fa7b"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:6b0c74f60c03eed4b5d19f866df79c1d1bffc4c61f9bf31b114402c47680997f"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:bbba42e244a0ebc1639c62ab44e4a172767d3721d2af48f2764ca00de7721479"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0b9f76f47b6df8c6aaa02a27fdff52e6aaf64d39296683ed06d0ec9acf2515d2"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:c52ce4b4bfe8e0c2cf102f7b71cca00fc3228113e71712597940c9c340ae31b1"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:0d3ae040c91f500814df6557276462c0c869b16168ef51d01c8a7da150f54513"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-win32.whl", hash = "sha256:112ecc4825c5d362298d1e3c512d3f942c1a74f26ca69dc4b19a4f2cd95cb764"},
- {file = "rapidfuzz-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:b1feec7407df54045bc9d4dce3431ce20855c1ff4dd170480fbace62164f8f9c"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8b352fe56b92bd2aa4ceae550543a923996c16efecf8f981c955dd5f522d2002"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9454f46bc4007be9148f18143bb1b615a740a99737a38cf7b9baf3c495d5d17c"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c1a75c87a2f4c9709c6e3ecdbb2317f0964ac96f845f6a331d8a437a2944d24"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:02f8b282a940cb749b1c51196baab7abb5590fcc8c065ce540c5d8414366036d"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2def32a4228a717c5e6a699f0742546aee4091eb1e59e79781ceacabfc54452c"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2e9d494ff51b942ed1504f84c13476319c89fc9bcc6379cc0816b776a7994199"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:e94a6af7f0cc8ff49ab22842af255d8d927ca3b168b1a7e8e0784f1a2f49bc38"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c4ee8b9baaf46447dcaed925ad1d3606d3a375dfc5c67d1f3e33c46a3008cb5a"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:10e236b3ce5851f584bbf178e7fb04ae5d0fbb008f3bc580ef6185bbbb346cd1"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:068404913619182739fa3fde3079c17e3402744a1117df7f60055db331095a01"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-win32.whl", hash = "sha256:e933e3ce2d88b7584248493abcba2cd27240f42bf73ca040babfd1ce8036750e"},
- {file = "rapidfuzz-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:46f5931f7441e13574d0fe33e897212d00ff63f69c0db1d449afbc5e87bafd7f"},
- {file = "rapidfuzz-1.5.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7fb25ee0340cc26dad0bb4a97019bf61b4cefaec67a1be64ac9dac2f98c697cd"},
- {file = "rapidfuzz-1.5.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:e738ec4e680bebe4442befda5cdd18020c3721d4cd75f9bfe2fb94e78ef55618"},
- {file = "rapidfuzz-1.5.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:cb083609923bc4ac602e6f1d61be61a25b35cccfb5ee208d2aa89eb0be357c69"},
- {file = "rapidfuzz-1.5.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:08ecef2995b6ed1187b375d8f28ba4557522f098a1515b6afb0e3b452997a3a4"},
- {file = "rapidfuzz-1.5.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b6ff10d856fce55e2b1c681e4e7cd7da9b9eb6854571df60d6ed8904c777e64b"},
- {file = "rapidfuzz-1.5.1-pp37-pypy37_pp73-manylinux1_x86_64.whl", hash = "sha256:b41c346f16cd1ee71b259106d3cfad3347bd8fff4ff20f334a12738df6736c01"},
- {file = "rapidfuzz-1.5.1-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:f7148a53a0fd3466b82b81d94ad91aee7ce7947f37f16f9fb54319ea7df7f4af"},
- {file = "rapidfuzz-1.5.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:31d83af9ac39f47f47ce4830ee118e6fa53964cccd8161e9a478a326f2a994cf"},
- {file = "rapidfuzz-1.5.1.tar.gz", hash = "sha256:4ebbd071425ee812548c301c60661a4f8faa5e5bcc97a6f0bef5b562585a8025"},
+ {file = "rapidfuzz-1.9.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:68227a8b25291d6a2140aef049271ea30a77be5ef672a58e582a55a5cc1fce93"},
+ {file = "rapidfuzz-1.9.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c33541995b96ff40025c1456b8c74b7dd2ab9cbf91943fc35a7bb621f48940e2"},
+ {file = "rapidfuzz-1.9.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c2fafbbf97a4632822248f4201601b691e2eac5fdb30e5d7a96d07a6d058a7d4"},
+ {file = "rapidfuzz-1.9.1-cp27-cp27m-win32.whl", hash = "sha256:364795f617a99e1dbb55ac3947ab8366588b72531cb2d6152666287d20610706"},
+ {file = "rapidfuzz-1.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:f171d9e66144b0647f9b998ef10bdd919a640e4b1357250c8ef6259deb5ffe0d"},
+ {file = "rapidfuzz-1.9.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c83801a7c5209663aa120b815a4f2c39e95fe8e0b774ec58a1e0affd6a2fcfc6"},
+ {file = "rapidfuzz-1.9.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:67e61c2baa6bb1848c4a33752f1781124dcc90bf3f31b18b44db1ae4e4e26634"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8ab7eb003a18991347174910f11d38ff40399081185d9e3199ec277535f7828b"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5ad450badf06ddf98a246140b5059ba895ee8445e8102a5a289908327f551f81"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:402b2174bded62a793c5f7d9aec16bc32c661402360a934819ae72b54cfbce1e"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92066ccb054efc2e17afb4049c98b550969653cd58f71dd756cfcc8e6864630a"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8dc0bf1814accee08a9c9bace6672ef06eae6b0446fce88e3e97e23dfaf3ea10"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdbd387efb8478605951344f327dd03bf053c138d757369a43404305b99e55db"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-win32.whl", hash = "sha256:b1c54807e556dbcc6caf4ce0f24446c01b195f3cc46e2a6e74b82d3a21eaa45d"},
+ {file = "rapidfuzz-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac3273364cd1619cab3bf0ba731efea5405833f9eba362da7dcd70bd42073d8e"},
+ {file = "rapidfuzz-1.9.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:d9faf62606c08a0a6992dd480c72b6a068733ae02688dc35f2e36ba0d44673f4"},
+ {file = "rapidfuzz-1.9.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:f6a56a48be047637b1b0b2459a11cf7cd5aa7bbe16a439bd4f73b4af39e620e4"},
+ {file = "rapidfuzz-1.9.1-cp35-cp35m-win32.whl", hash = "sha256:aa91609979e9d2700f0ff100df99b36e7d700b70169ee385d43d5de9e471ae97"},
+ {file = "rapidfuzz-1.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b4cfdd0915ab4cec86c2ff6bab9f01b03454f3de0963c37f9f219df2ddf42b95"},
+ {file = "rapidfuzz-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6bfa4ad0158a093cd304f795ceefdc3861ae6942a61432b2a50858be6de88ca"},
+ {file = "rapidfuzz-1.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:eb0ea02295d9278bd2dcd2df4760b0f2887b6c3f2f374005ec5af320d8d3a37e"},
+ {file = "rapidfuzz-1.9.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5187cd5cd6273e9fee07de493a42a2153134a4914df74cb1abb0744551c548a"},
+ {file = "rapidfuzz-1.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6e5b8af63f9c05b64454460759ed84a715d581d598ec4484f4ec512f398e8b1"},
+ {file = "rapidfuzz-1.9.1-cp36-cp36m-win32.whl", hash = "sha256:36137f88f2b28115af506118e64e11c816611eab2434293af7fdacd1290ffb9d"},
+ {file = "rapidfuzz-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:fcc420cad46be7c9887110edf04cdee545f26dbf22650a443d89790fc35f7b88"},
+ {file = "rapidfuzz-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b06de314f426aebff8a44319016bbe2b22f7848c84e44224f80b0690b7b08b18"},
+ {file = "rapidfuzz-1.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e5de44e719faea79e45322b037f0d4a141d750b80d2204fa68f43a42a24f0fbc"},
+ {file = "rapidfuzz-1.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f9439df09a782afd01b67005a3b110c70bbf9e1cf06d2ac9b293ce2d02d3c549"},
+ {file = "rapidfuzz-1.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e903d4702647465721e2d0431c95f04fd56a06577f06f41e2960c83fd63c1bad"},
+ {file = "rapidfuzz-1.9.1-cp37-cp37m-win32.whl", hash = "sha256:a5298f4ac1975edcbb15583eab659a44b33aebaf3bccf172e185cfea68771c08"},
+ {file = "rapidfuzz-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:103193a01921b54fcdad6b01cfda3a68e00aeafca236b7ecd5b1b2c2e7e96337"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1d98a3187040dca855e02179a35c137f72ef83ce243783d44ea59efa86b94b3a"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb92bf7fc911b787055a88d9295ca3b4fe8576e3b59271f070f1b1b181eb087d"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3f014a0f5f8159a94c6ee884fedd1c30e07fb866a5d76ff2c18091bc6363b76f"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:31474074a99f72289ac325fbd77983e7d355d48860bfe7a4f6f6396fdb24410a"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec67d79af5a2d7b0cf67b570a5579710e461cadda4120478e813b63491f394dd"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ebc0d3d15ed32f98f0052cf6e3e9c9b8010fb93c04fb74d2022e3c51ec540e2"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-win32.whl", hash = "sha256:477ab1a3044bab89db45caabc562b158f68765ecaa638b73ba17e92f09dfa5ff"},
+ {file = "rapidfuzz-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:8e872763dc0367d7544aa585d2e8b27af233323b8a7cd2f9b78cafa05bae5018"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8401c41e219ae36ca7a88762776a6270511650d4cc70d024ae61561e96d67e47"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ea10bd8e0436801c3264f7084a5ea194f12ba9fe1ba898aa4a2107d276501292"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:433737914b46c1ffa0c678eceae1c260dc6b7fb5b6cad4c725d3e3607c764b32"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c3b08e90e45acbc469d1f456681643256e952bf84ec7714f58979baba0c8a1c"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bbcd265b3c86176e5db4cbba7b4364d7333c214ee80e2d259c7085929934ca9d"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d69fabcd635783cd842e7d5ee4b77164314c5124b82df5a0c436ab3d698f8a9"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-win32.whl", hash = "sha256:01f16b6f3fa5d1a26c12f5da5de0032f1e12c919d876005b57492a8ec9a5c043"},
+ {file = "rapidfuzz-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:0bcc5bbfdbe6068cc2cf0029ab6cde08dceac498d232fa3a61dd34fbfa0b3f36"},
+ {file = "rapidfuzz-1.9.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:de869c8f4e8edb9b2f7b8232a04896645501defcbd9d85bc0202ff3ec6285f6b"},
+ {file = "rapidfuzz-1.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:db5978e970fb0955974d51021da4b929e2e4890fef17792989ee32658e2b159c"},
+ {file = "rapidfuzz-1.9.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:33479f75f36ac3a1d8421365d4fa906e013490790730a89caba31d06e6f71738"},
+ {file = "rapidfuzz-1.9.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:af991cb333ec526d894923163050931b3a870b7694bf7687aaa6154d341a98f5"},
+ {file = "rapidfuzz-1.9.1.tar.gz", hash = "sha256:bd7a4fe33ba49db3417f0f57a8af02462554f1296dedcf35b026cd3525efef74"},
]
redis = [
- {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
- {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
+ {file = "redis-4.0.2-py3-none-any.whl", hash = "sha256:c8481cf414474e3497ec7971a1ba9b998c8efad0f0d289a009a5bbef040894f9"},
+ {file = "redis-4.0.2.tar.gz", hash = "sha256:ccf692811f2c1fc7a92b466aa2599e4a6d2d73d5f736a2c70be600657c0da34a"},
]
sentry-sdk = [
{file = "sentry-sdk-0.20.3.tar.gz", hash = "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237"},
@@ -1394,70 +1496,165 @@ six = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
snowballstemmer = [
- {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"},
- {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"},
+ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
+ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
sortedcontainers = [
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
]
+soupsieve = [
+ {file = "soupsieve-2.3.1-py3-none-any.whl", hash = "sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb"},
+ {file = "soupsieve-2.3.1.tar.gz", hash = "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"},
+]
taskipy = [
- {file = "taskipy-1.8.1-py3-none-any.whl", hash = "sha256:2b98f499966e40175d1f1306a64587f49dfa41b90d0d86c8f28b067cc58d0a56"},
- {file = "taskipy-1.8.1.tar.gz", hash = "sha256:7a2404125817e45d80e13fa663cae35da6e8ba590230094e815633653e25f98f"},
+ {file = "taskipy-1.9.0-py3-none-any.whl", hash = "sha256:02bd2c51c7356ed3f7f8853210ada1cd2ab273e68359ee865021c3057eec6615"},
+ {file = "taskipy-1.9.0.tar.gz", hash = "sha256:449c160b557cdb1d9c17097a5ea4aa0cd5223723ddbaaa5d5032dd16274fb8f0"},
+]
+testfixtures = [
+ {file = "testfixtures-6.18.3-py2.py3-none-any.whl", hash = "sha256:6ddb7f56a123e1a9339f130a200359092bd0a6455e31838d6c477e8729bb7763"},
+ {file = "testfixtures-6.18.3.tar.gz", hash = "sha256:2600100ae96ffd082334b378e355550fef8b4a529a6fa4c34f47130905c7426d"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
typing-extensions = [
- {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
- {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
- {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
+ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
+ {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"},
]
urllib3 = [
- {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
- {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
+ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"},
+ {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"},
]
virtualenv = [
- {file = "virtualenv-20.7.2-py2.py3-none-any.whl", hash = "sha256:e4670891b3a03eb071748c569a87cceaefbf643c5bac46d996c5a45c34aa0f06"},
- {file = "virtualenv-20.7.2.tar.gz", hash = "sha256:9ef4e8ee4710826e98ff3075c9a4739e2cb1040de6a2a8d35db0055840dc96a0"},
+ {file = "virtualenv-20.11.0-py2.py3-none-any.whl", hash = "sha256:eb0cb34160f32c6596405308ee6a8a4abbf3247b2b9794ae655a156d43abf48e"},
+ {file = "virtualenv-20.11.0.tar.gz", hash = "sha256:2f15b9226cb74b59c21e8236dd791c395bee08cdd33b99cddd18e1f866cdb098"},
+]
+wrapt = [
+ {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"},
+ {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"},
+ {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"},
+ {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"},
+ {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"},
+ {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"},
+ {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"},
+ {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"},
+ {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"},
+ {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"},
+ {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"},
+ {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"},
+ {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"},
+ {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"},
+ {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"},
+ {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"},
+ {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"},
+ {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"},
+ {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"},
+ {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"},
+ {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"},
+ {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"},
+ {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"},
+ {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"},
+ {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"},
+ {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"},
+ {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"},
+ {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"},
+ {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"},
+ {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"},
+ {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"},
+ {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"},
+ {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"},
+ {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"},
+ {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"},
+ {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"},
+ {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"},
+ {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"},
+ {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"},
+ {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"},
+ {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"},
+ {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"},
+ {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"},
+ {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"},
+ {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"},
+ {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"},
+ {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"},
+ {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"},
+ {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"},
+ {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"},
+ {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"},
]
yarl = [
- {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"},
- {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"},
- {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"},
- {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"},
- {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"},
- {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"},
- {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"},
- {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"},
- {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"},
- {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"},
- {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"},
- {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"},
- {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"},
- {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"},
- {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"},
- {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"},
- {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"},
+ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"},
+ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"},
+ {file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"},
+ {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"},
+ {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"},
+ {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"},
+ {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"},
+ {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"},
+ {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"},
+ {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"},
+ {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"},
+ {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"},
+ {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"},
+ {file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"},
+ {file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"},
+ {file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"},
+ {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"},
+ {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"},
+ {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"},
+ {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"},
+ {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"},
+ {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"},
+ {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"},
+ {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"},
+ {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"},
+ {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"},
+ {file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"},
+ {file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"},
+ {file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"},
+ {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"},
+ {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"},
+ {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"},
+ {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"},
+ {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"},
+ {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"},
+ {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"},
+ {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"},
+ {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"},
+ {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"},
+ {file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"},
+ {file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"},
+ {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"},
+ {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"},
+ {file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"},
+ {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"},
+ {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"},
+ {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"},
+ {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"},
+ {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"},
+ {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"},
+ {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"},
+ {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"},
+ {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"},
+ {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"},
+ {file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"},
+ {file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"},
+ {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"},
+ {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"},
+ {file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"},
+ {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"},
+ {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"},
+ {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"},
+ {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"},
+ {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"},
+ {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"},
+ {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"},
+ {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"},
+ {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"},
+ {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"},
+ {file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"},
+ {file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"},
+ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"},
]
diff --git a/pyproject.toml b/pyproject.toml
index 7848f593..a72fa706 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,24 +12,28 @@ aiodns = "~=2.0"
aioredis = "~1.3"
rapidfuzz = "~=1.4"
arrow = "~=1.1.0"
-pillow = "~=8.1"
+beautifulsoup4 = "~=4.9"
+pillow = "~=9.0"
sentry-sdk = "~=0.19"
PyYAML = "~=5.4"
async-rediscache = {extras = ["fakeredis"], version = "~=0.1.4"}
emojis = "~=0.6.0"
-matplotlib = "~=3.4.1"
+coloredlogs = "~=15.0"
+colorama = { version = "~=0.4.3", markers = "sys_platform == 'win32'" }
+lxml = "~=4.6"
+emoji = "^1.6.1"
[tool.poetry.dev-dependencies]
flake8 = "~=3.8"
flake8-annotations = "~=2.3"
flake8-bugbear = "~=20.1"
flake8-docstrings = "~=1.5"
-flake8-import-order = "~=0.18"
flake8-string-format = "~=0.3"
flake8-tidy-imports = "~=4.1"
flake8-todo = "~=0.7"
+flake8-isort = "~=4.0"
pep8-naming = "~=0.11"
-pip-licenses = "~=3.5.2"
+pip-licenses = "~=3.5"
pre-commit = "~=2.1"
python-dotenv = "~=0.15"
taskipy = "~=1.6"
@@ -38,7 +42,17 @@ taskipy = "~=1.6"
start = "python -m bot"
lint = "pre-commit run --all-files"
precommit = "pre-commit install"
+isort = "isort ."
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
+
+[tool.isort]
+multi_line_output = 6
+order_by_type = false
+case_sensitive = true
+combine_as_imports = true
+line_length = 120
+atomic = true
+known_first_party = ["bot"]
diff --git a/sir-lancebot-logo.png b/sir-lancebot-logo.png
index fc606bf7..d8e28ad8 100644
--- a/sir-lancebot-logo.png
+++ b/sir-lancebot-logo.png
Binary files differ
diff --git a/tox.ini b/tox.ini
index af87e6fc..61ff9616 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,12 +11,16 @@ ignore=
# Docstring Quotes
D301,D302,
# Docstring Content
- D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417
+ D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417,
# Type Annotations
- ANN002,ANN003,ANN101,ANN102,ANN204,ANN206
+ ANN002,ANN003,ANN101,ANN102,ANN204,ANN206,
+ # Binary operators over multiple lines
+ W504,
exclude=
__pycache__,.cache,
venv,.venv,
tests,
- constants.py
+per-file-ignores =
+ # Don't require docstrings in constants
+ constants.py:D101
import-order-style=pycharm