aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Amrou Bellalouna <[email protected]>2023-03-09 07:34:54 +0100
committerGravatar GitHub <[email protected]>2023-03-09 09:34:54 +0300
commit12fe15baa5f3ea5d35d43d27ff671dca5fe58334 (patch)
treeb8c3b61f6a0ee959c8d6e2748e2d8a04c97e1321
parentMerge pull request #2449 from python-discord/update-deps (diff)
Merge #2408: Scaffold server config via a bootstrapping script
Refactor configuration into a pydantic-based python constants file, and add a utility to auto-populate guild data. Squashed commits: * use basic config for demo purposes * fix guiding comments * update var names for proper context reflection * fix wront iteration var * add all roles, & channels * load categories * separate sections in env file * ignore .env.server * rename change_log to changelog This also adds a default env file to look for * remove instantiation of webhooks * add most of the default configs These will mostly be fetched from the .env.default file, which won't be bootstrapped * warn when categories/roles/channels are not found * add env file to keep server defaults * fix malformatted value in the .env.default * add default server env variables * update the sections formatting in default env file * fallback to server env when loading constants * add guild basic defaults * update change_log channel name to changelog * add the Guid settings prefix * make _Guild inherit from EnvConfig * add webhook defaults * add python_news defaults to the server env * ad missing webhooks prefix * update bootstrapper logger name * update priority of the env loaded files According to Pydantic's docs: "Later files in the list/tuple will take priority over earlier files." * warn user that default value from PyDis' config will be used * add colours default config * add antispam config * update antispam references * add redis default cfg * add Stats, Cooldowns and CleanMessages consts This also includes their default values * add Metabase to constants This also includes its default values * add URLS to constants This also includes its default values * use the Field class to provide defaults This avoids overriding & changing the `fields` of the `Config` class "dynamically" * add keys constant class * add Guild conf * replace dash with underscore in script * appease linter * transform attributes of AntiSpam to dict when needed This ensures that the application stays backwards compatible * add root_validator for the colours class This enables the conversion from hex to int easily since it's not a supported type by pydantic * reinstate the role & channels combinations * rename URLS to URLs * add emojis & icons constants * add filter constants & their default values * remove all useless spaces * instantiate the keys class * add bot prefix to default env file * fetch Bot constants from env vars instead of the prefix ones * add Miscellaneous config * instantiate poor forgotten Miscellaneous config * add final touches to the constants module This includes removing dups, adding missing channels & fixing type casts * move all default values to constants.py This is done by using the `Field` class. It allows us to 1. Set defaults, in case the variables are not configured 2. Load them from a env variable under a specific name (for backwards comp) 3. load it from any env variable file that contains the right prefix * ignore all .env files * load BOT_TOKEN & GUILD_ID from .env * allow _GUILD to read its id from the `GUILD_ID` env var * base Webhooks settings off of a Webhook model * create necessary webhooks if non existent * appease flake8 docstrings error * make the script idempotent * update type hints * uppercase all consts * make webhook channel optional * add httpx to its own dependency group This group will be optional & only related to the bootstrapper * replace requests with httpx * pass client as param * include raise_for_status as a response hook * rename get_webhook to webhook_exists * update docstring of the constants module * use "." as a separator * update script to account for already created webhooks * make ANTI_SPAM_RULES a module level constant This ensures that flake8 doesn't complain about making a function call in the function's signature * remove the manual resolving of .env paths * update usages of AntiSpam constants * remove forgotten assignment of rule_config * remove useless assignments of env file names * delete default config-default.yml * update docstrings of CodeBlockCog to reference constants.py * add a poetry task that runs the bootstrapping script * add python-dotenv to the config-bootstrap group * update hook name to _raise_for_status * construct site_api in _URLs * remove __name__ == '__main__'guard * Revert "construct site_api in _URLs" This reverts commit 1c555c4280c6a0bdd452319cbd3ffcd0370f5d48. * remove usage of the Field class * update env var keys that the bootstrapping script needs * use API_KEYS.SITE_API as env var in docker compose instead of BOT_API_KEY * use basic config for demo purposes * fix guiding comments * update var names for proper context reflection * fix wront iteration var * add all roles, & channels * load categories * separate sections in env file * ignore .env.server * rename change_log to changelog This also adds a default env file to look for * remove instantiation of webhooks * add most of the default configs These will mostly be fetched from the .env.default file, which won't be bootstrapped * warn when categories/roles/channels are not found * add env file to keep server defaults * fix malformatted value in the .env.default * add default server env variables * update the sections formatting in default env file * fallback to server env when loading constants * add guild basic defaults * update change_log channel name to changelog * add the Guid settings prefix * make _Guild inherit from EnvConfig * add webhook defaults * add python_news defaults to the server env * ad missing webhooks prefix * update bootstrapper logger name * update priority of the env loaded files According to Pydantic's docs: "Later files in the list/tuple will take priority over earlier files." * warn user that default value from PyDis' config will be used * add colours default config * add antispam config * update antispam references * add redis default cfg * add Stats, Cooldowns and CleanMessages consts This also includes their default values * add Metabase to constants This also includes its default values * add URLS to constants This also includes its default values * use the Field class to provide defaults This avoids overriding & changing the `fields` of the `Config` class "dynamically" * add keys constant class * add Guild conf * replace dash with underscore in script * appease linter * transform attributes of AntiSpam to dict when needed This ensures that the application stays backwards compatible * add root_validator for the colours class This enables the conversion from hex to int easily since it's not a supported type by pydantic * reinstate the role & channels combinations * rename URLS to URLs * add emojis & icons constants * add filter constants & their default values * remove all useless spaces * instantiate the keys class * add bot prefix to default env file * fetch Bot constants from env vars instead of the prefix ones * add Miscellaneous config * instantiate poor forgotten Miscellaneous config * add final touches to the constants module This includes removing dups, adding missing channels & fixing type casts * move all default values to constants.py This is done by using the `Field` class. It allows us to 1. Set defaults, in case the variables are not configured 2. Load them from a env variable under a specific name (for backwards comp) 3. load it from any env variable file that contains the right prefix * ignore all .env files * load BOT_TOKEN & GUILD_ID from .env * allow _GUILD to read its id from the `GUILD_ID` env var * base Webhooks settings off of a Webhook model * create necessary webhooks if non existent * appease flake8 docstrings error * make the script idempotent * update type hints * uppercase all consts * make webhook channel optional * add httpx to its own dependency group This group will be optional & only related to the bootstrapper * replace requests with httpx * pass client as param * include raise_for_status as a response hook * rename get_webhook to webhook_exists * update docstring of the constants module * use "." as a separator * update script to account for already created webhooks * make ANTI_SPAM_RULES a module level constant This ensures that flake8 doesn't complain about making a function call in the function's signature * remove the manual resolving of .env paths * update usages of AntiSpam constants * remove forgotten assignment of rule_config * remove useless assignments of env file names * delete default config-default.yml * update docstrings of CodeBlockCog to reference constants.py * add a poetry task that runs the bootstrapping script * add python-dotenv to the config-bootstrap group * update hook name to _raise_for_status * construct site_api in _URLs * remove __name__ == '__main__'guard * Revert "construct site_api in _URLs" This reverts commit 1c555c4280c6a0bdd452319cbd3ffcd0370f5d48. * remove usage of the Field class * update env var keys that the bootstrapping script needs * use API_KEYS.SITE_API as env var in docker compose instead of BOT_API_KEY * relock dependencies * update snekbox's defaults * add support for ot channels * rename help_system_forum to python_help * rename nomination_archive to nomination_voting_archive * rename appeals2 to appeals_2 * yeet sprinters role out * rename all big_brother_logs instances to big_brother The purpose is to adhere to what we have in prod * rename bootstrap_config.py to botstrap.py * update module name of the configure poetry task * update error messages to reflect the new keys needed for env variables * install dotenv as an extra with pydantic * update all prefixes to "_" (underscore) * log tuple of (channel_name, channel_id) in the config verifier * update needed default values for docker compose env var * relock dependencies * update forgotten delimiters & env prefixes
-rw-r--r--.gitignore1
-rw-r--r--bot/constants.py1106
-rw-r--r--bot/exts/backend/branding/_cog.py2
-rw-r--r--bot/exts/backend/config_verifier.py4
-rw-r--r--bot/exts/filters/antispam.py14
-rw-r--r--bot/exts/fun/duck_pond.py2
-rw-r--r--bot/exts/help_channels/_channel.py4
-rw-r--r--bot/exts/help_channels/_cog.py4
-rw-r--r--bot/exts/help_channels/_stats.py2
-rw-r--r--bot/exts/info/codeblock/_cog.py2
-rw-r--r--bot/exts/moderation/incidents.py6
-rw-r--r--bot/exts/moderation/watchchannels/_watchchannel.py2
-rw-r--r--bot/exts/moderation/watchchannels/bigbrother.py4
-rw-r--r--bot/exts/recruitment/talentpool/_review.py2
-rw-r--r--botstrap.py164
-rw-r--r--config-default.yml560
-rw-r--r--docker-compose.yml7
-rw-r--r--poetry.lock269
-rw-r--r--pyproject.toml4
19 files changed, 863 insertions, 1296 deletions
diff --git a/.gitignore b/.gitignore
index 6691dbea1..65a9af431 100644
--- a/.gitignore
+++ b/.gitignore
@@ -123,3 +123,4 @@ TEST-**.xml
# Mac OS .DS_Store, which is a file that stores custom attributes of its containing folder
.DS_Store
+*.env*
diff --git a/bot/constants.py b/bot/constants.py
index f1fb5471f..5e5173a63 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -1,703 +1,726 @@
"""
-Loads bot configuration from YAML files.
-By default, this simply loads the default
-configuration located at `config-default.yml`.
-If a file called `config.yml` is found in the
-project directory, the default configuration
-is recursively updated with any settings from
-the custom configuration. Any settings left
-out in the custom user configuration will stay
-their default values from `config-default.yml`.
+Loads bot configuration from environment variables
+and `.env` files. By default, this simply loads the
+default configuration defined thanks to the `default`
+keyword argument in each instance of the `Field` class
+If two files called `.env` and `.env.server` are found
+in the project directory, the values will be loaded
+from both of them, thus overlooking the predefined defaults.
+Any settings left out in the custom user configuration
+will default to the values passed to the `default` kwarg.
"""
import os
-from collections.abc import Mapping
from enum import Enum
-from pathlib import Path
-from typing import Dict, List, Optional
+from typing import Optional
-import yaml
+from pydantic import BaseModel, BaseSettings, root_validator
-try:
- import dotenv
- dotenv.load_dotenv()
-except ModuleNotFoundError:
- pass
+class EnvConfig(BaseSettings):
+ class Config:
+ env_file = ".env", ".env.server",
+ env_file_encoding = 'utf-8'
-def _env_var_constructor(loader, node):
- """
- Implements a custom YAML tag for loading optional environment
- variables. If the environment variable is set, returns the
- value of it. Otherwise, returns `None`.
- Example usage in the YAML configuration:
+class _Miscellaneous(EnvConfig):
+ debug = True
+ file_logs = False
- # Optional app configuration. Set `MY_APP_KEY` in the environment to use it.
- application:
- key: !ENV 'MY_APP_KEY'
- """
- default = None
+Miscellaneous = _Miscellaneous()
- # Check if the node is a plain string value
- if node.id == 'scalar':
- value = loader.construct_scalar(node)
- key = str(value)
- else:
- # The node value is a list
- value = loader.construct_sequence(node)
- if len(value) >= 2:
- # If we have at least two values, then we have both a key and a default value
- default = value[1]
- key = value[0]
- else:
- # Otherwise, we just have a key
- key = value[0]
+FILE_LOGS = Miscellaneous.file_logs
+DEBUG_MODE = Miscellaneous.debug
- return os.getenv(key, default)
+class _Bot(EnvConfig):
+ EnvConfig.Config.env_prefix = "bot_"
-def _join_var_constructor(loader, node):
- """
- Implements a custom YAML tag for concatenating other tags in
- the document to strings. This allows for a much more DRY configuration
- file.
- """
+ prefix = "!"
+ sentry_dsn = ""
+ token = ""
+ trace_loggers = "*"
- fields = loader.construct_sequence(node)
- return "".join(str(x) for x in fields)
+Bot = _Bot()
-yaml.SafeLoader.add_constructor("!ENV", _env_var_constructor)
-yaml.SafeLoader.add_constructor("!JOIN", _join_var_constructor)
-# Pointing old tag to !ENV constructor to avoid breaking existing configs
-yaml.SafeLoader.add_constructor("!REQUIRED_ENV", _env_var_constructor)
+class _Channels(EnvConfig):
+ EnvConfig.Config.env_prefix = "channels_"
+ announcements = 354619224620138496
+ changelog = 748238795236704388
+ mailing_lists = 704372456592506880
+ python_events = 729674110270963822
+ python_news = 704372456592506880
+ reddit = 458224812528238616
-with open("config-default.yml", encoding="UTF-8") as f:
- _CONFIG_YAML = yaml.safe_load(f)
+ dev_contrib = 635950537262759947
+ dev_core = 411200599653351425
+ dev_log = 622895325144940554
+ meta = 429409067623251969
+ python_general = 267624335836053506
-def _recursive_update(original, new):
- """
- Helper method which implements a recursive `dict.update`
- method, used for updating the original configuration with
- configuration specified by the user.
- """
+ python_help = 1035199133436354600
- for key, value in original.items():
- if key not in new:
- continue
+ attachment_log = 649243850006855680
+ filter_log = 1014943924185473094
+ message_log = 467752170159079424
+ mod_log = 282638479504965634
+ nomination_voting_archive = 833371042046148738
+ user_log = 528976905546760203
+ voice_log = 640292421988646961
- if isinstance(value, Mapping):
- if not any(isinstance(subvalue, Mapping) for subvalue in value.values()):
- original[key].update(new[key])
- _recursive_update(original[key], new[key])
- else:
- original[key] = new[key]
+ off_topic_0 = 291284109232308226
+ off_topic_1 = 463035241142026251
+ off_topic_2 = 463035268514185226
+ bot_commands = 267659945086812160
+ discord_bots = 343944376055103488
+ esoteric = 470884583684964352
+ voice_gate = 764802555427029012
+ code_jam_planning = 490217981872177157
-if Path("config.yml").exists():
- print("Found `config.yml` file, loading constants from it.")
- with open("config.yml", encoding="UTF-8") as f:
- user_config = yaml.safe_load(f)
- _recursive_update(_CONFIG_YAML, user_config)
+ # Staff
+ admins = 365960823622991872
+ admin_spam = 563594791770914816
+ defcon = 464469101889454091
+ helpers = 385474242440986624
+ incidents = 714214212200562749
+ incidents_archive = 720668923636351037
+ mod_alerts = 473092532147060736
+ mod_meta = 775412552795947058
+ mods = 305126844661760000
+ nominations = 822920136150745168
+ nomination_voting = 822853512709931008
+ organisation = 551789653284356126
+ # Staff announcement channels
+ admin_announcements = 749736155569848370
+ mod_announcements = 372115205867700225
+ staff_announcements = 464033278631084042
+ staff_info = 396684402404622347
+ staff_lounge = 464905259261755392
-def check_required_keys(keys):
- """
- Verifies that keys that are set to be required are present in the
- loaded configuration.
- """
- for key_path in keys:
- lookup = _CONFIG_YAML
- try:
- for key in key_path.split('.'):
- lookup = lookup[key]
- if lookup is None:
- raise KeyError(key)
- except KeyError:
- raise KeyError(
- f"A configuration for `{key_path}` is required, but was not found. "
- "Please set it in `config.yml` or setup an environment variable and try again."
- )
-
-
-try:
- required_keys = _CONFIG_YAML['config']['required_keys']
-except KeyError:
- pass
-else:
- check_required_keys(required_keys)
-
-
-class YAMLGetter(type):
+ # Voice Channels
+ admins_voice = 500734494840717332
+ code_help_voice_0 = 751592231726481530
+ code_help_voice_1 = 764232549840846858
+ general_voice_0 = 751591688538947646
+ general_voice_1 = 799641437645701151
+ staff_voice = 412375055910043655
+
+ black_formatter = 846434317021741086
+
+ # Voice Chat
+ code_help_chat_0 = 755154969761677312
+ code_help_chat_1 = 766330079135268884
+ staff_voice_chat = 541638762007101470
+ voice_chat_0 = 412357430186344448
+ voice_chat_1 = 799647045886541885
+
+ big_brother = 468507907357409333
+ duck_pond = 637820308341915648
+ roles = 851270062434156586
+
+
+Channels = _Channels()
+
+
+class _Roles(EnvConfig):
+
+ EnvConfig.Config.env_prefix = "roles_"
+
+ # Self-assignable roles, see the Subscribe cog
+ advent_of_code = 518565788744024082
+ announcements = 463658397560995840
+ lovefest = 542431903886606399
+ pyweek_announcements = 897568414044938310
+ revival_of_code = 988801794668908655
+ legacy_help_channels_access = 1074780483776417964
+
+ contributors = 295488872404484098
+ help_cooldown = 699189276025421825
+ muted = 277914926603829249
+ partners = 323426753857191936
+ python_community = 458226413825294336
+ voice_verified = 764802720779337729
+
+ # Streaming
+ video = 764245844798079016
+
+ # Staff
+ admins = 267628507062992896
+ core_developers = 587606783669829632
+ code_jam_event_team = 787816728474288181
+ devops = 409416496733880320
+ domain_leads = 807415650778742785
+ events_lead = 778361735739998228
+ helpers = 267630620367257601
+ moderators = 831776746206265384
+ mod_team = 267629731250176001
+ owners = 267627879762755584
+ project_leads = 815701647526330398
+
+ # Code Jam
+ jammers = 737249140966162473
+
+ # Patreon
+ patreon_tier_1 = 505040943800516611
+ patreon_tier_2 = 743399725914390631
+ patreon_tier_3 = 743400204367036520
+
+
+Roles = _Roles()
+
+
+class _Categories(EnvConfig):
+ EnvConfig.Config.env_prefix = "categories_"
+
+ logs = 468520609152892958
+ moderators = 749736277464842262
+ modmail = 714494672835444826
+ appeals = 890331800025563216
+ appeals_2 = 895417395261341766
+ voice = 356013253765234688
+
+ # 2021 Summer Code Jam
+ summer_code_jam = 861692638540857384
+
+
+Categories = _Categories()
+
+
+class _Guild(EnvConfig):
+ EnvConfig.Config.env_prefix = "guild_"
+
+ id = 267624335836053506
+ invite = "https://discord.gg/python"
+
+ moderation_categories = [
+ Categories.moderators,
+ Categories.modmail,
+ Categories.logs,
+ Categories.appeals,
+ Categories.appeals_2
+ ]
+ moderation_channels = [Channels.admins, Channels.admin_spam, Channels.mods]
+ modlog_blacklist = [
+ Channels.attachment_log,
+ Channels.message_log,
+ Channels.mod_log,
+ Channels.staff_voice,
+ Channels.filter_log
+ ]
+ reminder_whitelist = [Channels.bot_commands, Channels.dev_contrib, Channels.black_formatter]
+ moderation_roles = [Roles.admins, Roles.mod_team, Roles.moderators, Roles.owners]
+ staff_roles = [Roles.admins, Roles.helpers, Roles.mod_team, Roles.owners]
+
+
+Guild = _Guild()
+
+
+class Event(Enum):
"""
- Implements a custom metaclass used for accessing
- configuration data by simply accessing class attributes.
- Supports getting configuration from up to two levels
- of nested configuration through `section` and `subsection`.
-
- `section` specifies the YAML configuration section (or "key")
- in which the configuration lives, and must be set.
-
- `subsection` is an optional attribute specifying the section
- within the section from which configuration should be loaded.
-
- Example Usage:
-
- # config.yml
- bot:
- prefixes:
- direct_message: ''
- guild: '!'
-
- # config.py
- class Prefixes(metaclass=YAMLGetter):
- section = "bot"
- subsection = "prefixes"
-
- # Usage in Python code
- from config import Prefixes
- def get_prefix(bot, message):
- if isinstance(message.channel, PrivateChannel):
- return Prefixes.direct_message
- return Prefixes.guild
+ Event names. This does not include every event (for example, raw
+ events aren't here), but only events used in ModLog for now.
"""
- subsection = None
+ guild_channel_create = "guild_channel_create"
+ guild_channel_delete = "guild_channel_delete"
+ guild_channel_update = "guild_channel_update"
+ guild_role_create = "guild_role_create"
+ guild_role_delete = "guild_role_delete"
+ guild_role_update = "guild_role_update"
+ guild_update = "guild_update"
+
+ member_join = "member_join"
+ member_remove = "member_remove"
+ member_ban = "member_ban"
+ member_unban = "member_unban"
+ member_update = "member_update"
- def __getattr__(cls, name):
- name = name.lower()
+ message_delete = "message_delete"
+ message_edit = "message_edit"
- try:
- if cls.subsection is not None:
- return _CONFIG_YAML[cls.section][cls.subsection][name]
- return _CONFIG_YAML[cls.section][name]
- except KeyError as e:
- dotted_path = '.'.join(
- (cls.section, cls.subsection, name)
- if cls.subsection is not None else (cls.section, name)
- )
- print(f"Tried accessing configuration variable at `{dotted_path}`, but it could not be found.")
- raise AttributeError(repr(name)) from e
+ voice_state_update = "voice_state_update"
- def __getitem__(cls, name):
- return cls.__getattr__(name)
- def __iter__(cls):
- """Return generator of key: value pairs of current constants class' config values."""
- for name in cls.__annotations__:
- yield name, getattr(cls, name)
+class ThreadArchiveTimes(Enum):
+ HOUR = 60
+ DAY = 1440
+ THREE_DAY = 4320
+ WEEK = 10080
-# Dataclasses
-class Bot(metaclass=YAMLGetter):
- section = "bot"
+class Webhook(BaseModel):
+ id: int
+ channel: Optional[int]
- prefix: str
- sentry_dsn: Optional[str]
- token: str
- trace_loggers: Optional[str]
+class _Webhooks(EnvConfig):
+ EnvConfig.Config.env_prefix = "webhooks_"
+ EnvConfig.Config.env_nested_delimiter = '_'
-class Redis(metaclass=YAMLGetter):
- section = "bot"
- subsection = "redis"
+ big_brother: Webhook = Webhook(id=569133704568373283, channel=Channels.big_brother)
+ dev_log: Webhook = Webhook(id=680501655111729222, channel=Channels.dev_log)
+ duck_pond: Webhook = Webhook(id=637821475327311927, channel=Channels.duck_pond)
+ incidents: Webhook = Webhook(id=816650601844572212, channel=Channels.incidents)
+ incidents_archive: Webhook = Webhook(id=720671599790915702, channel=Channels.incidents_archive)
+ python_news: Webhook = Webhook(id=704381182279942324, channel=Channels.python_news)
- host: str
- password: Optional[str]
- port: int
- use_fakeredis: bool # If this is True, Bot will use fakeredis.aioredis
+Webhooks = _Webhooks()
-class Filter(metaclass=YAMLGetter):
- section = "filter"
- filter_domains: bool
- filter_everyone_ping: bool
- filter_invites: bool
- filter_zalgo: bool
- watch_regex: bool
- watch_rich_embeds: bool
+class _BigBrother(EnvConfig):
+ EnvConfig.Config.env_prefix = "big_brother_"
- # Notifications are not expected for "watchlist" type filters
+ header_message_limit = 15
+ log_delay = 15
- notify_user_domains: bool
- notify_user_everyone_ping: bool
- notify_user_invites: bool
- notify_user_zalgo: bool
- offensive_msg_delete_days: int
- ping_everyone: bool
+BigBrother = _BigBrother()
- channel_whitelist: List[int]
- role_whitelist: List[int]
+class _CodeBlock(EnvConfig):
+ EnvConfig.Config.env_prefix = "code_block_"
-class Cooldowns(metaclass=YAMLGetter):
- section = "bot"
- subsection = "cooldowns"
+ # The channels in which code blocks will be detected. They are not subject to a cooldown.
+ channel_whitelist: list[int] = [Channels.bot_commands]
+ # The channels which will be affected by a cooldown. These channels are also whitelisted.
+ cooldown_channels: list[int] = [Channels.python_general]
- tags: int
+ cooldown_seconds = 300
+ minimum_lines = 4
-class Colours(metaclass=YAMLGetter):
- section = "style"
- subsection = "colours"
+CodeBlock = _CodeBlock()
- blue: int
- bright_green: int
- orange: int
- pink: int
- purple: int
- soft_green: int
- soft_orange: int
- soft_red: int
- white: int
- yellow: int
+class _Colours(EnvConfig):
+ EnvConfig.Config.env_prefix = "colours_"
-class DuckPond(metaclass=YAMLGetter):
- section = "duck_pond"
+ blue = 0x3775a8
+ bright_green = 0x01d277
+ orange = 0xe67e22
+ pink = 0xcf84e0
+ purple = 0xb734eb
+ soft_green = 0x68c290
+ soft_orange = 0xf9cb54
+ soft_red = 0xcd6d6d
+ white = 0xfffffe
+ yellow = 0xffd241
- threshold: int
- channel_blacklist: List[int]
+ @root_validator(pre=True)
+ def parse_hex_values(cls, values):
+ for key, value in values.items():
+ values[key] = int(value, 16)
+ return values
-class Emojis(metaclass=YAMLGetter):
- section = "style"
- subsection = "emojis"
+Colours = _Colours()
- badge_bug_hunter: str
- badge_bug_hunter_level_2: str
- badge_early_supporter: str
- badge_hypesquad: str
- badge_hypesquad_balance: str
- badge_hypesquad_bravery: str
- badge_hypesquad_brilliance: str
- badge_partner: str
- badge_staff: str
- badge_verified_bot_developer: str
- verified_bot: str
- bot: str
- defcon_shutdown: str # noqa: E704
- defcon_unshutdown: str # noqa: E704
- defcon_update: str # noqa: E704
+class _Free(EnvConfig):
+ EnvConfig.Config.env_prefix = "free_"
- failmail: str
+ activity_timeout = 600
+ cooldown_per = 60.0
+ cooldown_rate = 1
- incident_actioned: str
- incident_investigating: str
- incident_unactioned: str
- status_dnd: str
- status_idle: str
- status_offline: str
- status_online: str
+Free = _Free()
- ducky_dave: str
- trashcan: str
+class Punishment(BaseModel):
+ remove_after = 600
+ role_id: int = Roles.muted
- bullet: str
- check_mark: str
- cross_mark: str
- new: str
- pencil: str
- ok_hand: str
+class Rule(BaseModel):
+ interval: int
+ max: int
-class Icons(metaclass=YAMLGetter):
- section = "style"
- subsection = "icons"
+# Some help in choosing an appropriate name for this is appreciated
+class ExtendedRule(Rule):
+ max_consecutive: int
- crown_blurple: str
- crown_green: str
- crown_red: str
- defcon_denied: str # noqa: E704
- defcon_shutdown: str # noqa: E704
- defcon_unshutdown: str # noqa: E704
- defcon_update: str # noqa: E704
+class Rules(BaseModel):
+ attachments: Rule = Rule(interval=10, max=10)
+ burst: Rule = Rule(interval=10, max=7)
+ chars: Rule = Rule(interval=5, max=200)
+ discord_emojis: Rule = Rule(interval=10, max=20)
+ duplicates: Rule = Rule(interval=10, max=3)
+ links: Rule = Rule(interval=10, max=10)
+ mentions: Rule = Rule(interval=10, max=5)
+ newlines: ExtendedRule = ExtendedRule(interval=10, max=100, max_consecutive=10)
+ role_mentions: Rule = Rule(interval=10, max=3)
- filtering: str
- green_checkmark: str
- green_questionmark: str
- guild_update: str
+class _AntiSpam(EnvConfig):
+ EnvConfig.Config.env_prefix = 'anti_spam_'
+ EnvConfig.Config.env_nested_delimiter = '_'
- hash_blurple: str
- hash_green: str
- hash_red: str
+ cache_size = 100
- message_bulk_delete: str
- message_delete: str
- message_edit: str
+ clean_offending = True
+ ping_everyone = True
- pencil: str
+ punishment = Punishment()
+ rules = Rules()
- questionmark: str
- remind_blurple: str
- remind_green: str
- remind_red: str
+AntiSpam = _AntiSpam()
- sign_in: str
- sign_out: str
- superstarify: str
- unsuperstarify: str
+class _HelpChannels(EnvConfig):
+ EnvConfig.Config.env_prefix = "help_channels_"
- token_removed: str
+ enable = True
+ idle_minutes = 30
+ deleted_idle_minutes = 5
+ # Roles which are allowed to use the command which makes channels dormant
+ cmd_whitelist: list[int] = [Roles.helpers]
- user_ban: str
- user_mute: str
- user_unban: str
- user_unmute: str
- user_update: str
- user_verified: str
- user_warn: str
- voice_state_blue: str
- voice_state_green: str
- voice_state_red: str
+HelpChannels = _HelpChannels()
-class CleanMessages(metaclass=YAMLGetter):
- section = "bot"
- subsection = "clean"
+class _RedirectOutput(EnvConfig):
+ EnvConfig.Config.env_prefix = "redirect_output_"
- message_limit: int
+ delete_delay = 15
+ delete_invocation = True
-class Stats(metaclass=YAMLGetter):
- section = "bot"
- subsection = "stats"
+RedirectOutput = _RedirectOutput()
- presence_update_timeout: int
- statsd_host: str
+class _DuckPond(EnvConfig):
+ EnvConfig.Config.env_prefix = "duck_pond_"
-class Categories(metaclass=YAMLGetter):
- section = "guild"
- subsection = "categories"
+ threshold = 7
- moderators: int
- modmail: int
- voice: int
+ channel_blacklist: list[str] = [
+ Channels.announcements,
+ Channels.python_news,
+ Channels.python_events,
+ Channels.mailing_lists,
+ Channels.reddit,
+ Channels.duck_pond,
+ Channels.changelog,
+ Channels.staff_announcements,
+ Channels.mod_announcements,
+ Channels.admin_announcements,
+ Channels.staff_info
+ ]
- # 2021 Summer Code Jam
- summer_code_jam: int
-
-
-class Channels(metaclass=YAMLGetter):
- section = "guild"
- subsection = "channels"
-
- announcements: int
- change_log: int
- mailing_lists: int
- python_events: int
- python_news: int
- reddit: int
-
- dev_contrib: int
- dev_core: int
- dev_log: int
-
- meta: int
- python_general: int
-
- help_system_forum: int
-
- attachment_log: int
- filter_log: int
- message_log: int
- mod_log: int
- nomination_archive: int
- user_log: int
- voice_log: int
-
- off_topic_0: int
- off_topic_1: int
- off_topic_2: int
-
- bot_commands: int
- discord_bots: int
- esoteric: int
- voice_gate: int
- code_jam_planning: int
-
- admins: int
- admin_spam: int
- defcon: int
- helpers: int
- incidents: int
- incidents_archive: int
- mod_alerts: int
- mod_meta: int
- mods: int
- nominations: int
- nomination_voting: int
- organisation: int
-
- admin_announcements: int
- mod_announcements: int
- staff_announcements: int
-
- admins_voice: int
- code_help_voice_0: int
- code_help_voice_1: int
- general_voice_0: int
- general_voice_1: int
- staff_voice: int
-
- code_help_chat_0: int
- code_help_chat_1: int
- staff_voice_chat: int
- voice_chat_0: int
- voice_chat_1: int
-
- big_brother_logs: int
-
- roles: int
-
-
-class Webhooks(metaclass=YAMLGetter):
- section = "guild"
- subsection = "webhooks"
-
- big_brother: int
- dev_log: int
- duck_pond: int
- incidents: int
- incidents_archive: int
-
-
-class Roles(metaclass=YAMLGetter):
- section = "guild"
- subsection = "roles"
- # Self-assignable roles, see the Subscribe cog
- advent_of_code: int
- announcements: int
- lovefest: int
- pyweek_announcements: int
- revival_of_code: int
- legacy_help_channels_access: int
-
- contributors: int
- help_cooldown: int
- muted: int
- partners: int
- python_community: int
- sprinters: int
- voice_verified: int
- video: int
-
- admins: int
- core_developers: int
- code_jam_event_team: int
- devops: int
- domain_leads: int
- events_lead: int
- helpers: int
- moderators: int
- mod_team: int
- owners: int
- project_leads: int
-
- jammers: int
-
- patreon_tier_1: int
- patreon_tier_2: int
- patreon_tier_3: int
-
-
-class Guild(metaclass=YAMLGetter):
- section = "guild"
+DuckPond = _DuckPond()
- id: int
- invite: str # Discord invite, gets embedded in chat
- moderation_categories: List[int]
- moderation_channels: List[int]
- modlog_blacklist: List[int]
- reminder_whitelist: List[int]
- moderation_roles: List[int]
- staff_roles: List[int]
+class _PythonNews(EnvConfig):
+ EnvConfig.Config.env_prefix = "python_news_"
+
+ channel: int = Webhooks.python_news.channel
+ webhook: int = Webhooks.python_news.id
+ mail_lists = ['python-ideas', 'python-announce-list', 'pypi-announce', 'python-dev']
+
+
+PythonNews = _PythonNews()
+
+
+class _VoiceGate(EnvConfig):
+ EnvConfig.Config.env_prefix = "voice_gate_"
+
+ bot_message_delete_delay = 10
+ minimum_activity_blocks = 3
+ minimum_days_member = 3
+ minimum_messages = 50
+ voice_ping_delete_delay = 60
+
+
+VoiceGate = _VoiceGate()
+
+
+class _Branding(EnvConfig):
+ EnvConfig.Config.env_prefix = "branding_"
+
+ cycle_frequency = 3
-class Keys(metaclass=YAMLGetter):
- section = "keys"
+Branding = _Branding()
- github: Optional[str]
- site_api: Optional[str]
+class _VideoPermission(EnvConfig):
+ EnvConfig.Config.env_prefix = "video_permission_"
-class URLs(metaclass=YAMLGetter):
- section = "urls"
+ default_permission_duration = 5
+
+
+VideoPermission = _VideoPermission()
+
+
+class _Redis(EnvConfig):
+ EnvConfig.Config.env_prefix = "redis_"
+
+ host = "redis.default.svc.cluster.local"
+ password = ""
+ port = 6379
+ use_fakeredis = False # If this is True, Bot will use fakeredis.aioredis
+
+
+Redis = _Redis()
+
+
+class _CleanMessages(EnvConfig):
+ EnvConfig.Config.env_prefix = "clean_"
+
+ message_limit = 10_000
+
+
+CleanMessages = _CleanMessages()
+
+
+class _Stats(EnvConfig):
+ EnvConfig.Config.env_prefix = "stats_"
+
+ presence_update_timeout = 30
+ statsd_host = "graphite.default.svc.cluster.local"
+
+
+Stats = _Stats()
+
+
+class _Cooldowns(EnvConfig):
+ EnvConfig.Config.env_prefix = "cooldowns_"
+
+ tags = 60
+
+
+Cooldowns = _Cooldowns()
+
+
+class _Metabase(EnvConfig):
+ EnvConfig.Config.env_prefix = "metabase_"
+
+ username = ""
+ password = ""
+ base_url = "http://metabase.default.svc.cluster.local"
+ public_url = "https://metabase.pythondiscord.com"
+ max_session_age = 20_160
+
+
+Metabase = _Metabase()
+
+
+class _BaseURLs(EnvConfig):
+ EnvConfig.Config.env_prefix = "urls_"
# Snekbox endpoints
- snekbox_eval_api: str
- snekbox_311_eval_api: str
+ snekbox_eval_api = "http://snekbox-310.default.svc.cluster.local/eval"
+ snekbox_311_eval_api = "http://snekbox.default.svc.cluster.local/eval"
- # Discord API endpoints
- discord_api: str
- discord_invite_api: str
+ # Discord API
+ discord_api = "https://discordapp.com/api/v7/"
# Misc endpoints
- bot_avatar: str
- github_bot_repo: str
+ bot_avatar = "https://raw.githubusercontent.com/python-discord/branding/main/logos/logo_circle/logo_circle.png"
+
+ github_bot_repo = "https://github.com/python-discord/bot"
+
+ # Site
+ site = "pythondiscord.com"
+ site_schema = "https://"
+ site_api = "site.default.svc.cluster.local/api"
+ site_api_schema = "http://"
+
+
+BaseURLs = _BaseURLs()
+
+
+class _URLs(_BaseURLs):
+
+ # Discord API endpoints
+ discord_invite_api: str = "".join([BaseURLs.discord_api, "invites"])
# Base site vars
- connect_max_retries: int
- connect_cooldown: int
- site: str
- site_api: str
- site_schema: str
- site_api_schema: str
+ connect_max_retries = 3
+ connect_cooldown = 5
+
+ site_staff: str = "".join([BaseURLs.site_schema, BaseURLs.site, "/staff"])
+ site_paste = "".join(["paste.", BaseURLs.site])
# Site endpoints
- site_logs_view: str
- paste_service: str
+ site_logs_view: str = "".join([BaseURLs.site_schema, BaseURLs.site, "/staff/bot/logs"])
+ paste_service: str = "".join([BaseURLs.site_schema, "paste.", BaseURLs.site, "/{key}"])
-class Metabase(metaclass=YAMLGetter):
- section = "metabase"
+URLs = _URLs()
- username: Optional[str]
- password: Optional[str]
- base_url: str
- public_url: str
- max_session_age: int
+class _Emojis(EnvConfig):
+ EnvConfig.Config.env_prefix = "emojis_"
-class AntiSpam(metaclass=YAMLGetter):
- section = 'anti_spam'
+ badge_bug_hunter = "<:bug_hunter_lvl1:743882896372269137>"
+ badge_bug_hunter_level_2 = "<:bug_hunter_lvl2:743882896611344505>"
+ badge_early_supporter = "<:early_supporter:743882896909140058>"
+ badge_hypesquad = "<:hypesquad_events:743882896892362873>"
+ badge_hypesquad_balance = "<:hypesquad_balance:743882896460480625>"
+ badge_hypesquad_bravery = "<:hypesquad_bravery:743882896745693335>"
+ badge_hypesquad_brilliance = "<:hypesquad_brilliance:743882896938631248>"
+ badge_partner = "<:partner:748666453242413136>"
+ badge_staff = "<:discord_staff:743882896498098226>"
+ badge_verified_bot_developer = "<:verified_bot_dev:743882897299210310>"
+ verified_bot = "<:verified_bot:811645219220750347>"
+ bot = "<:bot:812712599464443914>"
- cache_size: int
+ defcon_shutdown = "<:defcondisabled:470326273952972810>" # noqa: E704
+ defcon_unshutdown = "<:defconenabled:470326274213150730>" # noqa: E704
+ defcon_update = "<:defconsettingsupdated:470326274082996224>" # noqa: E704
- clean_offending: bool
- ping_everyone: bool
+ failmail = "<:failmail:633660039931887616>"
- punishment: Dict[str, Dict[str, int]]
- rules: Dict[str, Dict[str, int]]
+ incident_actioned = "<:incident_actioned:714221559279255583>"
+ incident_investigating = "<:incident_investigating:714224190928191551>"
+ incident_unactioned = "<:incident_unactioned:714223099645526026>"
+ status_dnd = "<:status_dnd:470326272082313216>"
+ status_idle = "<:status_idle:470326266625785866>"
+ status_offline = "<:status_offline:470326266537705472>"
+ status_online = "<:status_online:470326272351010816>"
-class BigBrother(metaclass=YAMLGetter):
- section = 'big_brother'
+ ducky_dave = "<:ducky_dave:742058418692423772>"
- header_message_limit: int
- log_delay: int
+ trashcan = "<:trashcan:637136429717389331>"
+ bullet = "\u2022"
+ check_mark = "\u2705"
+ cross_mark = "\u274C"
+ new = "\U0001F195"
+ pencil = "\u270F"
-class CodeBlock(metaclass=YAMLGetter):
- section = 'code_block'
+ ok_hand = ":ok_hand:"
- channel_whitelist: List[int]
- cooldown_channels: List[int]
- cooldown_seconds: int
- minimum_lines: int
+Emojis = _Emojis()
-class Free(metaclass=YAMLGetter):
- section = 'free'
- activity_timeout: int
- cooldown_per: float
- cooldown_rate: int
+class _Icons(EnvConfig):
+ EnvConfig.Config.env_prefix = "icons_"
+ crown_blurple = "https://cdn.discordapp.com/emojis/469964153289965568.png"
+ crown_green = "https://cdn.discordapp.com/emojis/469964154719961088.png"
+ crown_red = "https://cdn.discordapp.com/emojis/469964154879344640.png"
-class HelpChannels(metaclass=YAMLGetter):
- section = 'help_channels'
+ defcon_denied = "https://cdn.discordapp.com/emojis/472475292078964738.png" # noqa: E704
+ defcon_shutdown = "https://cdn.discordapp.com/emojis/470326273952972810.png" # noqa: E704
+ defcon_unshutdown = "https://cdn.discordapp.com/emojis/470326274213150730.png" # noqa: E704
+ defcon_update = "https://cdn.discordapp.com/emojis/472472638342561793.png" # noqa: E704
- enable: bool
- idle_minutes: int
- deleted_idle_minutes: int
- cmd_whitelist: List[int]
+ filtering = "https://cdn.discordapp.com/emojis/472472638594482195.png"
+ green_checkmark = "https://raw.githubusercontent.com/python-discord/branding/main/icons/checkmark/green-checkmark-dist.png"
+ green_questionmark = "https://raw.githubusercontent.com/python-discord/branding/main/icons/checkmark/green-question-mark-dist.png"
+ guild_update = "https://cdn.discordapp.com/emojis/469954765141442561.png"
-class RedirectOutput(metaclass=YAMLGetter):
- section = 'redirect_output'
+ hash_blurple = "https://cdn.discordapp.com/emojis/469950142942806017.png"
+ hash_green = "https://cdn.discordapp.com/emojis/469950144918585344.png"
+ hash_red = "https://cdn.discordapp.com/emojis/469950145413251072.png"
- delete_delay: int
- delete_invocation: bool
+ message_bulk_delete = "https://cdn.discordapp.com/emojis/469952898994929668.png"
+ message_delete = "https://cdn.discordapp.com/emojis/472472641320648704.png"
+ message_edit = "https://cdn.discordapp.com/emojis/472472638976163870.png"
+ pencil = "https://cdn.discordapp.com/emojis/470326272401211415.png"
-class PythonNews(metaclass=YAMLGetter):
- section = 'python_news'
+ questionmark = "https://cdn.discordapp.com/emojis/512367613339369475.png"
- channel: int
- webhook: int
- mail_lists: List[str]
+ remind_blurple = "https://cdn.discordapp.com/emojis/477907609215827968.png"
+ remind_green = "https://cdn.discordapp.com/emojis/477907607785570310.png"
+ remind_red = "https://cdn.discordapp.com/emojis/477907608057937930.png"
+ sign_in = "https://cdn.discordapp.com/emojis/469952898181234698.png"
+ sign_out = "https://cdn.discordapp.com/emojis/469952898089091082.png"
-class VoiceGate(metaclass=YAMLGetter):
- section = "voice_gate"
+ superstarify = "https://cdn.discordapp.com/emojis/636288153044516874.png"
+ unsuperstarify = "https://cdn.discordapp.com/emojis/636288201258172446.png"
- bot_message_delete_delay: int
- minimum_activity_blocks: int
- minimum_days_member: int
- minimum_messages: int
- voice_ping_delete_delay: int
+ token_removed = "https://cdn.discordapp.com/emojis/470326273298792469.png"
+ user_ban = "https://cdn.discordapp.com/emojis/469952898026045441.png"
+ user_mute = "https://cdn.discordapp.com/emojis/472472640100106250.png"
+ user_unban = "https://cdn.discordapp.com/emojis/469952898692808704.png"
+ user_unmute = "https://cdn.discordapp.com/emojis/472472639206719508.png"
+ user_update = "https://cdn.discordapp.com/emojis/469952898684551168.png"
+ user_verified = "https://cdn.discordapp.com/emojis/470326274519334936.png"
+ user_warn = "https://cdn.discordapp.com/emojis/470326274238447633.png"
-class Branding(metaclass=YAMLGetter):
- section = "branding"
+ voice_state_blue = "https://cdn.discordapp.com/emojis/656899769662439456.png"
+ voice_state_green = "https://cdn.discordapp.com/emojis/656899770094452754.png"
+ voice_state_red = "https://cdn.discordapp.com/emojis/656899769905709076.png"
- cycle_frequency: int
+Icons = _Icons()
-class Event(Enum):
- """
- Event names. This does not include every event (for example, raw
- events aren't here), but only events used in ModLog for now.
- """
- guild_channel_create = "guild_channel_create"
- guild_channel_delete = "guild_channel_delete"
- guild_channel_update = "guild_channel_update"
- guild_role_create = "guild_role_create"
- guild_role_delete = "guild_role_delete"
- guild_role_update = "guild_role_update"
- guild_update = "guild_update"
+class _Filter(EnvConfig):
+ EnvConfig.Config.env_prefix = "filters_"
- member_join = "member_join"
- member_remove = "member_remove"
- member_ban = "member_ban"
- member_unban = "member_unban"
- member_update = "member_update"
+ filter_domains = True
+ filter_everyone_ping = True
+ filter_invites = True
+ filter_zalgo = False
+ watch_regex = True
+ watch_rich_embeds = True
- message_delete = "message_delete"
- message_edit = "message_edit"
+ # Notifications are not expected for "watchlist" type filters
- voice_state_update = "voice_state_update"
+ notify_user_domains = False
+ notify_user_everyone_ping = True
+ notify_user_invites = True
+ notify_user_zalgo = False
+ offensive_msg_delete_days = 7
+ ping_everyone = True
-class VideoPermission(metaclass=YAMLGetter):
- section = "video_permission"
+ channel_whitelist = [
+ Channels.admins,
+ Channels.big_brother,
+ Channels.dev_log,
+ Channels.message_log,
+ Channels.mod_log,
+ Channels.staff_lounge
+ ]
+ role_whitelist = [
+ Roles.admins,
+ Roles.helpers,
+ Roles.moderators,
+ Roles.owners,
+ Roles.python_community,
+ Roles.partners
+ ]
- default_permission_duration: int
+Filter = _Filter()
-class ThreadArchiveTimes(Enum):
- HOUR = 60
- DAY = 1440
- THREE_DAY = 4320
- WEEK = 10080
+class _Keys(EnvConfig):
+
+ EnvConfig.Config.env_prefix = "api_keys_"
+
+ github = ""
+ site_api = ""
+
+
+Keys = _Keys()
-# Debug mode
-DEBUG_MODE: bool = _CONFIG_YAML["debug"] == "true"
-FILE_LOGS: bool = _CONFIG_YAML["file_logs"].lower() == "true"
-# Paths
BOT_DIR = os.path.dirname(__file__)
PROJECT_ROOT = os.path.abspath(os.path.join(BOT_DIR, os.pardir))
@@ -715,6 +738,7 @@ MODERATION_CATEGORIES = Guild.moderation_categories
# Git SHA for Sentry
GIT_SHA = os.environ.get("GIT_SHA", "development")
+
# Bot replies
NEGATIVE_REPLIES = [
"Noooooo!!",
diff --git a/bot/exts/backend/branding/_cog.py b/bot/exts/backend/branding/_cog.py
index ff2704835..94429c172 100644
--- a/bot/exts/backend/branding/_cog.py
+++ b/bot/exts/backend/branding/_cog.py
@@ -325,7 +325,7 @@ class Branding(commands.Cog):
# Notify guild of new event ~ this reads the information that we cached above.
if event_changed and not event.meta.is_fallback:
- await self.send_info_embed(Channels.change_log, is_notification=True)
+ await self.send_info_embed(Channels.changelog, is_notification=True)
else:
log.trace("Omitting #changelog notification. Event has not changed, or new event is fallback.")
diff --git a/bot/exts/backend/config_verifier.py b/bot/exts/backend/config_verifier.py
index 97c8869a1..84ae5ca92 100644
--- a/bot/exts/backend/config_verifier.py
+++ b/bot/exts/backend/config_verifier.py
@@ -24,12 +24,12 @@ class ConfigVerifier(Cog):
server_channel_ids = {channel.id for channel in server.channels}
invalid_channels = [
- channel_name for channel_name, channel_id in constants.Channels
+ (channel_name, channel_id) for channel_name, channel_id in constants.Channels
if channel_id not in server_channel_ids
]
if invalid_channels:
- log.warning(f"Configured channels do not exist in server: {', '.join(invalid_channels)}.")
+ log.warning(f"Configured channels do not exist in server: {invalid_channels}.")
async def setup(bot: Bot) -> None:
diff --git a/bot/exts/filters/antispam.py b/bot/exts/filters/antispam.py
index d7783292d..4d2e67a31 100644
--- a/bot/exts/filters/antispam.py
+++ b/bot/exts/filters/antispam.py
@@ -41,6 +41,8 @@ RULE_FUNCTION_MAPPING = {
'role_mentions': rules.apply_role_mentions,
}
+ANTI_SPAM_RULES = AntiSpamConfig.rules.dict()
+
@dataclass
class DeletionContext:
@@ -121,7 +123,7 @@ class AntiSpam(Cog):
def __init__(self, bot: Bot, validation_errors: Dict[str, str]) -> None:
self.bot = bot
self.validation_errors = validation_errors
- role_id = AntiSpamConfig.punishment['role_id']
+ role_id = AntiSpamConfig.punishment.role_id
self.muted_role = Object(role_id)
self.expiration_date_converter = Duration()
@@ -129,7 +131,7 @@ class AntiSpam(Cog):
# Fetch the rule configuration with the highest rule interval.
max_interval_config = max(
- AntiSpamConfig.rules.values(),
+ ANTI_SPAM_RULES.values(),
key=itemgetter('interval')
)
self.max_interval = max_interval_config['interval']
@@ -178,8 +180,7 @@ class AntiSpam(Cog):
earliest_relevant_at = arrow.utcnow() - timedelta(seconds=self.max_interval)
relevant_messages = list(takewhile(lambda msg: msg.created_at > earliest_relevant_at, self.cache))
- for rule_name in AntiSpamConfig.rules:
- rule_config = AntiSpamConfig.rules[rule_name]
+ for rule_name, rule_config in ANTI_SPAM_RULES.items():
rule_function = RULE_FUNCTION_MAPPING[rule_name]
# Create a list of messages that were sent in the interval that the rule cares about.
@@ -229,7 +230,7 @@ class AntiSpam(Cog):
async def punish(self, msg: Message, member: Member, reason: str) -> None:
"""Punishes the given member for triggering an antispam rule."""
if not any(role.id == self.muted_role.id for role in member.roles):
- remove_role_after = AntiSpamConfig.punishment['remove_after']
+ remove_role_after = AntiSpamConfig.punishment.remove_after
# Get context and make sure the bot becomes the actor of infraction by patching the `author` attributes
context = await self.bot.get_context(msg)
@@ -297,10 +298,11 @@ class AntiSpam(Cog):
self.cache.update(after)
-def validate_config(rules_: Mapping = AntiSpamConfig.rules) -> Dict[str, str]:
+def validate_config(rules_: Mapping = ANTI_SPAM_RULES) -> Dict[str, str]:
"""Validates the antispam configs."""
validation_errors = {}
for name, config in rules_.items():
+ config = config
if name not in RULE_FUNCTION_MAPPING:
log.error(
f"Unrecognized antispam rule `{name}`. "
diff --git a/bot/exts/fun/duck_pond.py b/bot/exts/fun/duck_pond.py
index 1815e54f2..fee933b47 100644
--- a/bot/exts/fun/duck_pond.py
+++ b/bot/exts/fun/duck_pond.py
@@ -21,7 +21,7 @@ class DuckPond(Cog):
def __init__(self, bot: Bot):
self.bot = bot
- self.webhook_id = constants.Webhooks.duck_pond
+ self.webhook_id = constants.Webhooks.duck_pond.id
self.webhook = None
self.ducked_messages = []
self.relay_lock = None
diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py
index f64162006..670a10446 100644
--- a/bot/exts/help_channels/_channel.py
+++ b/bot/exts/help_channels/_channel.py
@@ -30,7 +30,7 @@ NEW_POST_ICON_URL = f"{BRANDING_REPO_RAW_URL}/main/icons/checkmark/green-checkma
CLOSED_POST_MSG = f"""
This help channel has been closed and it's no longer possible to send messages here. \
-If your question wasn't answered, feel free to create a new post in <#{constants.Channels.help_system_forum}>. \
+If your question wasn't answered, feel free to create a new post in <#{constants.Channels.python_help}>. \
To maximize your chances of getting a response, check out this guide on [asking good questions]({ASKING_GUIDE_URL}).
"""
CLOSED_POST_ICON_URL = f"{BRANDING_REPO_RAW_URL}/main/icons/zzz/zzz-dist.png"
@@ -39,7 +39,7 @@ CLOSED_POST_ICON_URL = f"{BRANDING_REPO_RAW_URL}/main/icons/zzz/zzz-dist.png"
def is_help_forum_post(channel: discord.abc.GuildChannel) -> bool:
"""Return True if `channel` is a post in the help forum."""
log.trace(f"Checking if #{channel} is a help channel.")
- return getattr(channel, "parent_id", None) == constants.Channels.help_system_forum
+ return getattr(channel, "parent_id", None) == constants.Channels.python_help
async def _close_help_post(closed_post: discord.Thread, closing_reason: _stats.ClosingReason) -> None:
diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py
index bc6bd0303..29d238a5c 100644
--- a/bot/exts/help_channels/_cog.py
+++ b/bot/exts/help_channels/_cog.py
@@ -39,9 +39,9 @@ class HelpForum(commands.Cog):
async def cog_load(self) -> None:
"""Archive all idle open posts, schedule check for later for active open posts."""
log.trace("Initialising help forum cog.")
- self.help_forum_channel = self.bot.get_channel(constants.Channels.help_system_forum)
+ self.help_forum_channel = self.bot.get_channel(constants.Channels.python_help)
if not isinstance(self.help_forum_channel, discord.ForumChannel):
- raise TypeError("Channels.help_system_forum is not a forum channel!")
+ raise TypeError("Channels.python_help is not a forum channel!")
for post in self.help_forum_channel.threads:
await _channel.maybe_archive_idle_post(post, self.scheduler, has_task=False)
diff --git a/bot/exts/help_channels/_stats.py b/bot/exts/help_channels/_stats.py
index 1075b439e..6ca40139b 100644
--- a/bot/exts/help_channels/_stats.py
+++ b/bot/exts/help_channels/_stats.py
@@ -22,7 +22,7 @@ class ClosingReason(Enum):
def report_post_count() -> None:
"""Report post count stats of the help forum."""
- help_forum = bot.instance.get_channel(constants.Channels.help_system_forum)
+ help_forum = bot.instance.get_channel(constants.Channels.python_help)
bot.instance.stats.gauge("help.total.in_use", len(help_forum.threads))
diff --git a/bot/exts/info/codeblock/_cog.py b/bot/exts/info/codeblock/_cog.py
index a431175fd..073a91a53 100644
--- a/bot/exts/info/codeblock/_cog.py
+++ b/bot/exts/info/codeblock/_cog.py
@@ -50,7 +50,7 @@ class CodeBlockCog(Cog, name="Code Block"):
The cog only detects messages in whitelisted channels. Channels may also have a cooldown on the
instructions being sent. Note all help channels are also whitelisted with cooldowns enabled.
- For configurable parameters, see the `code_block` section in config-default.py.
+ For configurable parameters, see the `_CodeBlock` class in constants.py.
"""
def __init__(self, bot: Bot):
diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py
index ce83ca3fe..3e06cc215 100644
--- a/bot/exts/moderation/incidents.py
+++ b/bot/exts/moderation/incidents.py
@@ -329,9 +329,9 @@ class Incidents(Cog):
await self.bot.wait_until_guild_available()
try:
- self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents)
+ self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents.id)
except discord.HTTPException:
- log.error(f"Failed to fetch incidents webhook with id `{Webhooks.incidents}`.")
+ log.error(f"Failed to fetch incidents webhook with id `{Webhooks.incidents.id}`.")
async def crawl_incidents(self) -> None:
"""
@@ -389,7 +389,7 @@ class Incidents(Cog):
embed, attachment_file = await make_embed(incident, outcome, actioned_by)
try:
- webhook = await self.bot.fetch_webhook(Webhooks.incidents_archive)
+ webhook = await self.bot.fetch_webhook(Webhooks.incidents_archive.id)
await webhook.send(
embed=embed,
username=sub_clyde(incident.author.display_name),
diff --git a/bot/exts/moderation/watchchannels/_watchchannel.py b/bot/exts/moderation/watchchannels/_watchchannel.py
index 2871eb5de..bc70a8c1d 100644
--- a/bot/exts/moderation/watchchannels/_watchchannel.py
+++ b/bot/exts/moderation/watchchannels/_watchchannel.py
@@ -53,7 +53,7 @@ class WatchChannel(metaclass=CogABCMeta):
) -> None:
self.bot = bot
- self.destination = destination # E.g., Channels.big_brother_logs
+ self.destination = destination # E.g., Channels.big_brother
self.webhook_id = webhook_id # E.g., Webhooks.big_brother
self.api_endpoint = api_endpoint # E.g., 'bot/infractions'
self.api_default_params = api_default_params # E.g., {'active': 'true', 'type': 'watch'}
diff --git a/bot/exts/moderation/watchchannels/bigbrother.py b/bot/exts/moderation/watchchannels/bigbrother.py
index 4a746edff..fe652cc5b 100644
--- a/bot/exts/moderation/watchchannels/bigbrother.py
+++ b/bot/exts/moderation/watchchannels/bigbrother.py
@@ -19,8 +19,8 @@ class BigBrother(WatchChannel, Cog, name="Big Brother"):
def __init__(self, bot: Bot) -> None:
super().__init__(
bot,
- destination=Channels.big_brother_logs,
- webhook_id=Webhooks.big_brother,
+ destination=Channels.big_brother,
+ webhook_id=Webhooks.big_brother.id,
api_endpoint='bot/infractions',
api_default_params={'active': 'true', 'type': 'watch', 'ordering': '-inserted_at', 'limit': 10_000},
logger=log
diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py
index b9e76b60f..efc9c7a4d 100644
--- a/bot/exts/recruitment/talentpool/_review.py
+++ b/bot/exts/recruitment/talentpool/_review.py
@@ -292,7 +292,7 @@ class Reviewer:
else:
embed_title = f"Vote for `{user_id}`"
- channel = self.bot.get_channel(Channels.nomination_archive)
+ channel = self.bot.get_channel(Channels.nomination_voting_archive)
for number, part in enumerate(
textwrap.wrap(embed_content, width=MAX_EMBED_SIZE, replace_whitespace=False, placeholder="")
):
diff --git a/botstrap.py b/botstrap.py
new file mode 100644
index 000000000..4b00be9aa
--- /dev/null
+++ b/botstrap.py
@@ -0,0 +1,164 @@
+import os
+import re
+from pathlib import Path
+
+from dotenv import load_dotenv
+from httpx import Client, HTTPStatusError, Response
+
+from bot.constants import Webhooks, _Categories, _Channels, _Roles
+from bot.log import get_logger
+
+load_dotenv()
+log = get_logger("Config Bootstrapper")
+
+env_file_path = Path(".env.server")
+BOT_TOKEN = os.getenv("BOT_TOKEN", None)
+GUILD_ID = os.getenv("GUILD_ID", None)
+
+
+if not BOT_TOKEN:
+ message = (
+ "Couldn't find BOT_TOKEN in the environment variables."
+ "Make sure to add it to the `.env` file likewise: `BOT_TOKEN=value_of_your_bot_token`"
+ )
+ log.warning(message)
+ raise ValueError(message)
+
+if not GUILD_ID:
+ message = (
+ "Couldn't find GUILD_ID in the environment variables."
+ "Make sure to add it to the `.env` file likewise: `GUILD_ID=value_of_your_discord_server_id`"
+ )
+ log.warning(message)
+ raise ValueError(message)
+
+
+class DiscordClient(Client):
+ """An HTTP client to communicate with Discord's APIs."""
+
+ def __init__(self):
+ super().__init__(
+ base_url="https://discord.com/api/v10",
+ headers={"Authorization": f"Bot {BOT_TOKEN}"},
+ event_hooks={"response": [self._raise_for_status]}
+ )
+
+ @staticmethod
+ def _raise_for_status(response: Response) -> None:
+ response.raise_for_status()
+
+
+def get_all_roles(guild_id: int | str, client: DiscordClient) -> dict:
+ """Fetches all the roles in a guild."""
+ result = {}
+
+ response = client.get(f"guilds/{guild_id}/roles")
+ roles = response.json()
+
+ for role in roles:
+ name = "_".join(part.lower() for part in role["name"].split(" ")).replace("-", "_")
+ result[name] = role["id"]
+
+ return result
+
+
+def get_all_channels_and_categories(
+ guild_id: int | str,
+ client: DiscordClient
+) -> tuple[dict[str, str], dict[str, str]]:
+ """Fetches all the text channels & categories in a guild."""
+ off_topic_channel_name_regex = r"ot\d{1}(_.*)+"
+ off_topic_count = 0
+ channels = {} # could be text channels only as well
+ categories = {}
+
+ response = client.get(f"guilds/{guild_id}/channels")
+ server_channels = response.json()
+
+ for channel in server_channels:
+ channel_type = channel["type"]
+ name = "_".join(part.lower() for part in channel["name"].split(" ")).replace("-", "_")
+ if re.match(off_topic_channel_name_regex, name):
+ name = f"off_topic_{off_topic_count}"
+ off_topic_count += 1
+
+ if channel_type == 4:
+ categories[name] = channel["id"]
+ else:
+ channels[name] = channel["id"]
+
+ return channels, categories
+
+
+def webhook_exists(webhook_id_: int, client: DiscordClient) -> bool:
+ """A predicate that indicates whether a webhook exists already or not."""
+ try:
+ client.get(f"webhooks/{webhook_id_}")
+ return True
+ except HTTPStatusError:
+ return False
+
+
+def create_webhook(name: str, channel_id_: int, client: DiscordClient) -> str:
+ """Creates a new webhook for a particular channel."""
+ payload = {"name": name}
+
+ response = client.post(f"channels/{channel_id_}/webhooks", json=payload)
+ new_webhook = response.json()
+ return new_webhook["id"]
+
+
+with DiscordClient() as discord_client:
+ config_str = "#Roles\n"
+
+ all_roles = get_all_roles(guild_id=GUILD_ID, client=discord_client)
+
+ for role_name in _Roles.__fields__:
+
+ role_id = all_roles.get(role_name, None)
+ if not role_id:
+ log.warning(f"Couldn't find the role {role_name} in the guild, PyDis' default values will be used.")
+ continue
+
+ config_str += f"roles_{role_name}={role_id}\n"
+
+ all_channels, all_categories = get_all_channels_and_categories(guild_id=GUILD_ID, client=discord_client)
+
+ config_str += "\n#Channels\n"
+
+ for channel_name in _Channels.__fields__:
+ channel_id = all_channels.get(channel_name, None)
+ if not channel_id:
+ log.warning(
+ f"Couldn't find the channel {channel_name} in the guild, PyDis' default values will be used."
+ )
+ continue
+
+ config_str += f"channels_{channel_name}={channel_id}\n"
+
+ config_str += "\n#Categories\n"
+
+ for category_name in _Categories.__fields__:
+ category_id = all_categories.get(category_name, None)
+ if not category_id:
+ log.warning(
+ f"Couldn't find the category {category_name} in the guild, PyDis' default values will be used."
+ )
+ continue
+
+ config_str += f"categories_{category_name}={category_id}\n"
+
+ env_file_path.write_text(config_str)
+
+ config_str += "\n#Webhooks\n"
+
+ for webhook_name, webhook_model in Webhooks:
+ webhook = webhook_exists(webhook_model.id, client=discord_client)
+ if not webhook:
+ webhook_channel_id = int(all_channels[webhook_name])
+ webhook_id = create_webhook(webhook_name, webhook_channel_id, client=discord_client)
+ else:
+ webhook_id = webhook_model.id
+ config_str += f"webhooks_{webhook_name}.id={webhook_id}\n"
+
+ env_file_path.write_text(config_str)
diff --git a/config-default.yml b/config-default.yml
deleted file mode 100644
index de0f7e4e8..000000000
--- a/config-default.yml
+++ /dev/null
@@ -1,560 +0,0 @@
-debug: !ENV ["BOT_DEBUG", "true"]
-file_logs: !ENV ["FILE_LOGS", "false"]
-
-
-bot:
- prefix: "!"
- sentry_dsn: !ENV "BOT_SENTRY_DSN"
- token: !ENV "BOT_TOKEN"
- trace_loggers: !ENV "BOT_TRACE_LOGGERS"
-
- clean:
- # Maximum number of messages to traverse for clean commands
- message_limit: 10000
-
- cooldowns:
- # Per channel, per tag.
- tags: 60
-
- redis:
- host: "redis.default.svc.cluster.local"
- password: !ENV "REDIS_PASSWORD"
- port: 6379
- use_fakeredis: false
-
- stats:
- presence_update_timeout: 300
- statsd_host: "graphite.default.svc.cluster.local"
-
-
-style:
- colours:
- blue: 0x3775a8
- bright_green: 0x01d277
- orange: 0xe67e22
- pink: 0xcf84e0
- purple: 0xb734eb
- soft_green: 0x68c290
- soft_orange: 0xf9cb54
- soft_red: 0xcd6d6d
- white: 0xfffffe
- yellow: 0xffd241
-
- emojis:
- badge_bug_hunter: "<:bug_hunter_lvl1:743882896372269137>"
- badge_bug_hunter_level_2: "<:bug_hunter_lvl2:743882896611344505>"
- badge_early_supporter: "<:early_supporter:743882896909140058>"
- badge_hypesquad: "<:hypesquad_events:743882896892362873>"
- badge_hypesquad_balance: "<:hypesquad_balance:743882896460480625>"
- badge_hypesquad_bravery: "<:hypesquad_bravery:743882896745693335>"
- badge_hypesquad_brilliance: "<:hypesquad_brilliance:743882896938631248>"
- badge_partner: "<:partner:748666453242413136>"
- badge_staff: "<:discord_staff:743882896498098226>"
- badge_verified_bot_developer: "<:verified_bot_dev:743882897299210310>"
- bot: "<:bot:812712599464443914>"
- verified_bot: "<:verified_bot:811645219220750347>"
-
- defcon_shutdown: "<:defcondisabled:470326273952972810>"
- defcon_unshutdown: "<:defconenabled:470326274213150730>"
- defcon_update: "<:defconsettingsupdated:470326274082996224>"
-
- failmail: "<:failmail:633660039931887616>"
-
- incident_actioned: "<:incident_actioned:714221559279255583>"
- incident_investigating: "<:incident_investigating:714224190928191551>"
- incident_unactioned: "<:incident_unactioned:714223099645526026>"
-
- status_dnd: "<:status_dnd:470326272082313216>"
- status_idle: "<:status_idle:470326266625785866>"
- status_offline: "<:status_offline:470326266537705472>"
- status_online: "<:status_online:470326272351010816>"
-
- ducky_dave: "<:ducky_dave:742058418692423772>"
-
- trashcan: "<:trashcan:637136429717389331>"
-
- bullet: "\u2022"
- check_mark: "\u2705"
- cross_mark: "\u274C"
- new: "\U0001F195"
- pencil: "\u270F"
-
- ok_hand: ":ok_hand:"
-
- icons:
- crown_blurple: "https://cdn.discordapp.com/emojis/469964153289965568.png"
- crown_green: "https://cdn.discordapp.com/emojis/469964154719961088.png"
- crown_red: "https://cdn.discordapp.com/emojis/469964154879344640.png"
-
- defcon_denied: "https://cdn.discordapp.com/emojis/472475292078964738.png"
- defcon_shutdown: "https://cdn.discordapp.com/emojis/470326273952972810.png"
- defcon_unshutdown: "https://cdn.discordapp.com/emojis/470326274213150730.png"
- defcon_update: "https://cdn.discordapp.com/emojis/472472638342561793.png"
-
- filtering: "https://cdn.discordapp.com/emojis/472472638594482195.png"
-
- green_checkmark: "https://raw.githubusercontent.com/python-discord/branding/main/icons/checkmark/green-checkmark-dist.png"
- green_questionmark: "https://raw.githubusercontent.com/python-discord/branding/main/icons/checkmark/green-question-mark-dist.png"
- guild_update: "https://cdn.discordapp.com/emojis/469954765141442561.png"
-
- hash_blurple: "https://cdn.discordapp.com/emojis/469950142942806017.png"
- hash_green: "https://cdn.discordapp.com/emojis/469950144918585344.png"
- hash_red: "https://cdn.discordapp.com/emojis/469950145413251072.png"
-
- message_bulk_delete: "https://cdn.discordapp.com/emojis/469952898994929668.png"
- message_delete: "https://cdn.discordapp.com/emojis/472472641320648704.png"
- message_edit: "https://cdn.discordapp.com/emojis/472472638976163870.png"
-
- pencil: "https://cdn.discordapp.com/emojis/470326272401211415.png"
-
- questionmark: "https://cdn.discordapp.com/emojis/512367613339369475.png"
-
- remind_blurple: "https://cdn.discordapp.com/emojis/477907609215827968.png"
- remind_green: "https://cdn.discordapp.com/emojis/477907607785570310.png"
- remind_red: "https://cdn.discordapp.com/emojis/477907608057937930.png"
-
- sign_in: "https://cdn.discordapp.com/emojis/469952898181234698.png"
- sign_out: "https://cdn.discordapp.com/emojis/469952898089091082.png"
-
- superstarify: "https://cdn.discordapp.com/emojis/636288153044516874.png"
- unsuperstarify: "https://cdn.discordapp.com/emojis/636288201258172446.png"
-
- token_removed: "https://cdn.discordapp.com/emojis/470326273298792469.png"
-
- user_ban: "https://cdn.discordapp.com/emojis/469952898026045441.png"
- user_mute: "https://cdn.discordapp.com/emojis/472472640100106250.png"
- user_unban: "https://cdn.discordapp.com/emojis/469952898692808704.png"
- user_unmute: "https://cdn.discordapp.com/emojis/472472639206719508.png"
- user_update: "https://cdn.discordapp.com/emojis/469952898684551168.png"
- user_verified: "https://cdn.discordapp.com/emojis/470326274519334936.png"
- user_warn: "https://cdn.discordapp.com/emojis/470326274238447633.png"
-
- voice_state_blue: "https://cdn.discordapp.com/emojis/656899769662439456.png"
- voice_state_green: "https://cdn.discordapp.com/emojis/656899770094452754.png"
- voice_state_red: "https://cdn.discordapp.com/emojis/656899769905709076.png"
-
-
-guild:
- id: 267624335836053506
- invite: "https://discord.gg/python"
-
- categories:
- logs: &LOGS 468520609152892958
- moderators: &MODS_CATEGORY 749736277464842262
- modmail: &MODMAIL 714494672835444826
- appeals: &APPEALS 890331800025563216
- appeals2: &APPEALS2 895417395261341766
- voice: 356013253765234688
- summer_code_jam: 861692638540857384
-
- channels:
- # Public announcement and news channels
- announcements: &ANNOUNCEMENTS 354619224620138496
- change_log: &CHANGE_LOG 748238795236704388
- mailing_lists: &MAILING_LISTS 704372456592506880
- python_events: &PYEVENTS_CHANNEL 729674110270963822
- python_news: &PYNEWS_CHANNEL 704372456592506880
- reddit: &REDDIT_CHANNEL 458224812528238616
-
- # Development
- dev_contrib: &DEV_CONTRIB 635950537262759947
- dev_core: &DEV_CORE 411200599653351425
- dev_voting: &DEV_CORE_VOTING 839162966519447552
- dev_log: &DEV_LOG 622895325144940554
-
- # Discussion
- meta: 429409067623251969
- python_general: &PY_GENERAL 267624335836053506
-
- # Python Help
- help_system_forum: 1035199133436354600
-
- # Topical
- discord_bots: 343944376055103488
-
- # Logs
- attachment_log: &ATTACH_LOG 649243850006855680
- filter_log: &FILTER_LOG 1014943924185473094
- message_log: &MESSAGE_LOG 467752170159079424
- mod_log: &MOD_LOG 282638479504965634
- nomination_archive: 833371042046148738
- user_log: 528976905546760203
- voice_log: 640292421988646961
-
- # Open Source Projects
- black_formatter: &BLACK_FORMATTER 846434317021741086
-
- # Off-topic
- off_topic_0: 291284109232308226
- off_topic_1: 463035241142026251
- off_topic_2: 463035268514185226
-
- # Special
- bot_commands: &BOT_CMD 267659945086812160
- esoteric: 470884583684964352
- voice_gate: 764802555427029012
- code_jam_planning: 490217981872177157
-
- # Staff
- admins: &ADMINS 365960823622991872
- admin_spam: &ADMIN_SPAM 563594791770914816
- defcon: &DEFCON 464469101889454091
- duck_pond: &DUCK_POND 637820308341915648
- helpers: &HELPERS 385474242440986624
- incidents: 714214212200562749
- incidents_archive: 720668923636351037
- mod_alerts: 473092532147060736
- mods: &MODS 305126844661760000
- mod_meta: 775412552795947058
- nominations: 822920136150745168
- nomination_voting: 822853512709931008
- organisation: &ORGANISATION 551789653284356126
- staff_lounge: &STAFF_LOUNGE 464905259261755392
- staff_info: &STAFF_INFO 396684402404622347
-
- # Staff announcement channels
- admin_announcements: &ADMIN_ANNOUNCEMENTS 749736155569848370
- mod_announcements: &MOD_ANNOUNCEMENTS 372115205867700225
- staff_announcements: &STAFF_ANNOUNCEMENTS 464033278631084042
-
- # Voice Channels
- admins_voice: &ADMINS_VOICE 500734494840717332
- code_help_voice_0: 751592231726481530
- code_help_voice_1: 764232549840846858
- general_voice_0: 751591688538947646
- general_voice_1: 799641437645701151
- staff_voice: &STAFF_VOICE 412375055910043655
-
- # Voice Chat
- code_help_chat_0: 755154969761677312
- code_help_chat_1: 766330079135268884
- staff_voice_chat: 541638762007101470
- voice_chat_0: 412357430186344448
- voice_chat_1: 799647045886541885
-
- # Watch
- big_brother_logs: &BB_LOGS 468507907357409333
-
- # Information
- roles: 851270062434156586
-
- moderation_categories:
- - *MODS_CATEGORY
- - *MODMAIL
- - *LOGS
- - *APPEALS
- - *APPEALS2
-
- moderation_channels:
- - *ADMINS
- - *ADMIN_SPAM
- - *MODS
-
- # Modlog cog explicitly ignores events which occur in these channels.
- # This is on top of implicitly ignoring events in channels that the mod team cannot view.
- modlog_blacklist:
- - *ATTACH_LOG
- - *MESSAGE_LOG
- - *MOD_LOG
- - *STAFF_VOICE
- - *FILTER_LOG
-
- reminder_whitelist:
- - *BOT_CMD
- - *DEV_CONTRIB
- - *BLACK_FORMATTER
-
- roles:
- # Self-assignable roles, see the Subscribe cog
- advent_of_code: 518565788744024082
- announcements: 463658397560995840
- lovefest: 542431903886606399
- pyweek_announcements: 897568414044938310
- revival_of_code: 988801794668908655
- legacy_help_channels_access: 1074780483776417964
-
- contributors: 295488872404484098
- help_cooldown: 699189276025421825
- muted: &MUTED_ROLE 277914926603829249
- partners: &PY_PARTNER_ROLE 323426753857191936
- python_community: &PY_COMMUNITY_ROLE 458226413825294336
- sprinters: &SPRINTERS 758422482289426471
- voice_verified: 764802720779337729
-
- # Staff
- admins: &ADMINS_ROLE 267628507062992896
- core_developers: 587606783669829632
- code_jam_event_team: 787816728474288181
- devops: 409416496733880320
- domain_leads: 807415650778742785
- events_lead: 778361735739998228
- helpers: &HELPERS_ROLE 267630620367257601
- moderators: &MODS_ROLE 831776746206265384
- mod_team: &MOD_TEAM_ROLE 267629731250176001
- owners: &OWNERS_ROLE 267627879762755584
- project_leads: 815701647526330398
-
- # Code Jam
- jammers: 737249140966162473
-
- # Streaming
- video: 764245844798079016
-
- # Patreon
- patreon_tier_1: 505040943800516611
- patreon_tier_2: 743399725914390631
- patreon_tier_3: 743400204367036520
-
- moderation_roles:
- - *ADMINS_ROLE
- - *MOD_TEAM_ROLE
- - *MODS_ROLE
- - *OWNERS_ROLE
-
- staff_roles:
- - *ADMINS_ROLE
- - *HELPERS_ROLE
- - *MOD_TEAM_ROLE
- - *OWNERS_ROLE
-
- webhooks:
- big_brother: 569133704568373283
- dev_log: 680501655111729222
- duck_pond: 637821475327311927
- incidents: 816650601844572212
- incidents_archive: 720671599790915702
- python_news: &PYNEWS_WEBHOOK 704381182279942324
-
-
-filter:
- # What do we filter?
- filter_domains: true
- filter_everyone_ping: true
- filter_invites: true
- filter_zalgo: false
- watch_regex: true
- watch_rich_embeds: true
-
- # Notify user on filter?
- # Notifications are not expected for "watchlist" type filters
- notify_user_domains: false
- notify_user_everyone_ping: true
- notify_user_invites: true
- notify_user_zalgo: false
-
- # Filter configuration
- offensive_msg_delete_days: 7 # How many days before deleting an offensive message?
- ping_everyone: true
-
- # Censor doesn't apply to these
- channel_whitelist:
- - *ADMINS
- - *BB_LOGS
- - *DEV_LOG
- - *MESSAGE_LOG
- - *MOD_LOG
- - *STAFF_LOUNGE
-
- role_whitelist:
- - *ADMINS_ROLE
- - *HELPERS_ROLE
- - *MODS_ROLE
- - *OWNERS_ROLE
- - *PY_COMMUNITY_ROLE
- - *SPRINTERS
- - *PY_PARTNER_ROLE
-
-
-keys:
- github: !ENV "GITHUB_API_KEY"
- site_api: !ENV "BOT_API_KEY"
-
-
-urls:
- # PyDis site vars
- connect_max_retries: 3
- connect_cooldown: 5
- site: &DOMAIN "pythondiscord.com"
- site_api: &API "site.default.svc.cluster.local/api"
- site_api_schema: "http://"
- site_paste: &PASTE !JOIN ["paste.", *DOMAIN]
- site_schema: &SCHEMA "https://"
- site_staff: &STAFF !JOIN [*SCHEMA, *DOMAIN, "/staff"]
-
- paste_service: !JOIN [*SCHEMA, *PASTE, "/{key}"]
- site_logs_view: !JOIN [*STAFF, "/bot/logs"]
-
- # Snekbox
- snekbox_eval_api: !ENV ["SNEKBOX_EVAL_API", "http://snekbox.default.svc.cluster.local/eval"]
- snekbox_311_eval_api: !ENV ["SNEKBOX_311_EVAL_API", "http://snekbox-311.default.svc.cluster.local/eval"]
-
- # Discord API URLs
- discord_api: &DISCORD_API "https://discordapp.com/api/v7/"
- discord_invite_api: !JOIN [*DISCORD_API, "invites"]
-
- # Misc URLsw
- bot_avatar: "https://raw.githubusercontent.com/python-discord/branding/main/logos/logo_circle/logo_circle.png"
- github_bot_repo: "https://github.com/python-discord/bot"
-
-
-anti_spam:
- cache_size: 100
-
- # Clean messages that violate a rule.
- clean_offending: true
- ping_everyone: true
-
- punishment:
- remove_after: 600
- role_id: *MUTED_ROLE
-
- rules:
- attachments:
- interval: 10
- max: 6
-
- burst:
- interval: 10
- max: 7
-
- # Burst shared it (temporarily) disabled to prevent
- # the bug that triggers multiple infractions/DMs per
- # user. It also tends to catch a lot of innocent users
- # now that we're so big.
- # burst_shared:
- # interval: 10
- # max: 20
-
- chars:
- interval: 5
- max: 4_200
-
- discord_emojis:
- interval: 10
- max: 20
-
- duplicates:
- interval: 10
- max: 3
-
- links:
- interval: 10
- max: 10
-
- mentions:
- interval: 10
- max: 5
-
- newlines:
- interval: 10
- max: 100
- max_consecutive: 10
-
- role_mentions:
- interval: 10
- max: 3
-
-
-metabase:
- username: !ENV "METABASE_USERNAME"
- password: !ENV "METABASE_PASSWORD"
- base_url: "http://metabase.default.svc.cluster.local"
- public_url: "https://metabase.pythondiscord.com"
- # 14 days, see https://www.metabase.com/docs/latest/operations-guide/environment-variables.html#max_session_age
- max_session_age: 20160
-
-
-big_brother:
- header_message_limit: 15
- log_delay: 15
-
-
-code_block:
- # The channels in which code blocks will be detected. They are not subject to a cooldown.
- channel_whitelist:
- - *BOT_CMD
-
- # The channels which will be affected by a cooldown. These channels are also whitelisted.
- cooldown_channels:
- - *PY_GENERAL
-
- # Sending instructions triggers a cooldown on a per-channel basis.
- # More instruction messages will not be sent in the same channel until the cooldown has elapsed.
- cooldown_seconds: 300
-
- # The minimum amount of lines a message or code block must have for instructions to be sent.
- minimum_lines: 4
-
-
-free:
- # Seconds to elapse for a channel
- # to be considered inactive.
- activity_timeout: 600
- cooldown_per: 60.0
- cooldown_rate: 1
-
-
-help_channels:
- enable: true
-
- # Allowed duration of inactivity before archiving a help post
- idle_minutes: 30
-
- # Allowed duration of inactivity when post is empty (due to deleted messages)
- # before archiving a help post
- deleted_idle_minutes: 5
-
- # Roles which are allowed to use the command which makes channels dormant
- cmd_whitelist:
- - *HELPERS_ROLE
-
-redirect_output:
- delete_delay: 15
- delete_invocation: true
-
-
-duck_pond:
- threshold: 7
- channel_blacklist:
- - *ANNOUNCEMENTS
- - *PYNEWS_CHANNEL
- - *PYEVENTS_CHANNEL
- - *MAILING_LISTS
- - *REDDIT_CHANNEL
- - *DUCK_POND
- - *CHANGE_LOG
- - *STAFF_ANNOUNCEMENTS
- - *MOD_ANNOUNCEMENTS
- - *ADMIN_ANNOUNCEMENTS
- - *STAFF_INFO
-
-
-python_news:
- channel: *PYNEWS_CHANNEL
- webhook: *PYNEWS_WEBHOOK
-
- mail_lists:
- - 'python-ideas'
- - 'python-announce-list'
- - 'pypi-announce'
- - 'python-dev'
-
-
-voice_gate:
- bot_message_delete_delay: 10 # Seconds before deleting bot's response in Voice Gate
- minimum_activity_blocks: 3 # Number of 10 minute blocks during which a user must have been active
- minimum_days_member: 3 # How many days the user must have been a member for
- minimum_messages: 50 # How many messages a user must have to be eligible for voice
- voice_ping_delete_delay: 60 # Seconds before deleting the bot's ping to user in Voice Gate
-
-
-branding:
- cycle_frequency: 3 # How many days bot wait before refreshing server icon
-
-
-config:
- required_keys: ['bot.token']
-
-
-video_permission:
- default_permission_duration: 5 # Default duration for stream command in minutes
diff --git a/docker-compose.yml b/docker-compose.yml
index bc53c482b..694f44507 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -112,4 +112,9 @@ services:
env_file:
- .env
environment:
- BOT_API_KEY: badbot13m0n8f570f942013fc818f234916ca531
+ API_KEYS_SITE_API: "badbot13m0n8f570f942013fc818f234916ca531"
+ URLS_SITE_API: "web:8000/api"
+ URLS_SNEKBOX_EVAL_API: "http://snekbox/eval"
+ URLS_SNEKBOX_311_EVAL_API: "http://snekbox-311/eval"
+ REDIS_HOST: "redis"
+ STATS_STATSD_HOST: "http://localhost"
diff --git a/poetry.lock b/poetry.lock
index 3bc0ea024..3ed2ade4b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -17,106 +17,106 @@ pycares = ">=4.0.0"
[[package]]
name = "aiohttp"
-version = "3.8.4"
+version = "3.8.3"
description = "Async http client/server framework (asyncio)"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
- {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"},
- {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"},
- {file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"},
- {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"},
- {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"},
- {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"},
- {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"},
- {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"},
- {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"},
- {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"},
- {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"},
- {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"},
- {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"},
- {file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"},
- {file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"},
- {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"},
- {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"},
- {file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"},
- {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"},
- {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"},
- {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"},
- {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"},
- {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654"},
- {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"},
- {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"},
- {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"},
- {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"},
- {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"},
- {file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"},
- {file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"},
- {file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"},
- {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"},
- {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"},
- {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"},
- {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"},
- {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622"},
- {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"},
- {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"},
- {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"},
- {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"},
- {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"},
- {file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"},
- {file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"},
- {file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"},
- {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"},
- {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"},
- {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"},
- {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"},
- {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"},
- {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"},
- {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"},
- {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"},
- {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"},
- {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"},
- {file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"},
- {file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"},
- {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"},
- {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"},
- {file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"},
- {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"},
- {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"},
- {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"},
- {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"},
- {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"},
- {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"},
- {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"},
- {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"},
- {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"},
- {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"},
- {file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"},
- {file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"},
- {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed"},
- {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"},
- {file = "aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"},
- {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"},
- {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf"},
- {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"},
- {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2"},
- {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e"},
- {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"},
- {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391"},
- {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"},
- {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14"},
- {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"},
- {file = "aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"},
- {file = "aiohttp-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04"},
- {file = "aiohttp-3.8.4.tar.gz", hash = "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c"},
+ {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"},
+ {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"},
+ {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"},
+ {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"},
+ {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"},
+ {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"},
+ {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"},
+ {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"},
+ {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"},
+ {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"},
+ {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"},
+ {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"},
+ {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"},
+ {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"},
+ {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"},
+ {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"},
+ {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"},
+ {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"},
+ {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"},
+ {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"},
+ {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"},
+ {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"},
+ {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"},
+ {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"},
+ {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"},
+ {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"},
+ {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"},
+ {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"},
+ {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"},
+ {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"},
+ {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"},
]
[package.dependencies]
aiosignal = ">=1.1.2"
async-timeout = ">=4.0.0a3,<5.0"
attrs = ">=17.3.0"
-charset-normalizer = ">=2.0,<4.0"
+charset-normalizer = ">=2.0,<3.0"
frozenlist = ">=1.1.1"
multidict = ">=4.5,<7.0"
yarl = ">=1.0,<2.0"
@@ -326,89 +326,19 @@ files = [
[[package]]
name = "charset-normalizer"
-version = "3.1.0"
+version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
-python-versions = ">=3.7.0"
-files = [
- {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"},
- {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"},
- {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"},
- {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"},
- {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"},
- {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"},
- {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"},
+python-versions = ">=3.6.0"
+files = [
+ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
+ {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
]
+[package.extras]
+unicode-backport = ["unicodedata2"]
+
[[package]]
name = "colorama"
version = "0.4.6"
@@ -1619,6 +1549,7 @@ files = [
]
[package.dependencies]
+python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
typing-extensions = ">=4.2.0"
[package.extras]
@@ -1787,7 +1718,7 @@ six = ">=1.5"
name = "python-dotenv"
version = "1.0.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -2454,4 +2385,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "3.10.*"
-content-hash = "55864b0999f050d425372702a3ee959010419e76f8269b5d2cfa239b84bf8c53"
+content-hash = "a6f4f1d677e9273746ab05c3bfe87ef0f7969fe42983d3f5cb7ab4ef10324d78"
diff --git a/pyproject.toml b/pyproject.toml
index 67f72e776..227ea4303 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,6 +11,7 @@ python = "3.10.*"
# See https://bot-core.pythondiscord.com/ for docs.
pydis_core = { version = "9.5.1", extras = ["async-rediscache"] }
+aiohttp = "3.8.3"
arrow = "1.2.3"
beautifulsoup4 = "4.11.2"
colorama = { version = "0.4.6", markers = "sys_platform == 'win32'" }
@@ -28,7 +29,7 @@ rapidfuzz = "2.13.7"
regex = "2022.10.31"
sentry-sdk = "1.16.0"
tldextract = "3.4.0"
-pydantic = "1.10.5"
+pydantic = { version = "1.10.5", extras = ["dotenv"]}
[tool.poetry.dev-dependencies]
coverage = "7.2.1"
@@ -45,7 +46,6 @@ pre-commit = "3.1.1"
pip-licenses = "4.1.0"
pytest = "7.2.2"
pytest-cov = "4.0.0"
-python-dotenv = "1.0.0"
pytest-subtests = "0.10.0"
pytest-xdist = "3.2.0"
taskipy = "1.10.3"