Skip to content

Commit 0231712

Browse files
Merge branch 'main' into new-appeals-process
2 parents a958852 + 7346131 commit 0231712

File tree

123 files changed

+1541
-954
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+1541
-954
lines changed

.github/workflows/lint-test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@ jobs:
8181
pip install poetry
8282
poetry install
8383
84-
# Check all the dependencies are compatible with the MIT license.
84+
# Check all of our non-dev dependencies are compatible with the MIT license.
8585
# If you added a new dependencies that is being rejected,
8686
# please make sure it is compatible with the license for this project,
8787
# and add it to the ALLOWED_LICENSE variable
8888
- name: Check Dependencies License
89-
run: pip-licenses --allow-only="$ALLOWED_LICENSE"
89+
run: |
90+
pip-licenses --allow-only="$ALLOWED_LICENSE" \
91+
--package $(poetry export -f requirements.txt --without-hashes | sed "s/==.*//g" | tr "\n" " ")
9092
9193
# This step caches our pre-commit environment. To make sure we
9294
# do create a new environment when our pre-commit setup changes,

.pre-commit-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ repos:
1313
rev: v1.5.1
1414
hooks:
1515
- id: python-check-blanket-noqa
16+
- repo: https://github.com/pycqa/isort
17+
rev: 5.8.0
18+
hooks:
19+
- id: isort
20+
name: isort (python)
1621
- repo: local
1722
hooks:
1823
- id: flake8

bot/__init__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55

66
from discord.ext import commands
77

8-
from bot import log
9-
from bot.command import Command
8+
from bot import log, monkey_patches
109

1110
if TYPE_CHECKING:
1211
from bot.bot import Bot
@@ -17,9 +16,11 @@
1716
if os.name == "nt":
1817
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
1918

19+
monkey_patches.patch_typing()
20+
2021
# Monkey-patch discord.py decorators to use the Command subclass which supports root aliases.
2122
# Must be patched before any cogs are added.
22-
commands.command = partial(commands.command, cls=Command)
23-
commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command)
23+
commands.command = partial(commands.command, cls=monkey_patches.Command)
24+
commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=monkey_patches.Command)
2425

2526
instance: "Bot" = None # Global Bot instance.

bot/__main__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import logging
2-
31
import aiohttp
42

53
import bot
64
from bot import constants
75
from bot.bot import Bot, StartupError
8-
from bot.log import setup_sentry
6+
from bot.log import get_logger, setup_sentry
97

108
setup_sentry()
119

@@ -21,7 +19,7 @@
2119
message = "Could not connect to Redis. Is it running?"
2220

2321
# The exception is logged with an empty message so the actual message is visible at the bottom
24-
log = logging.getLogger("bot")
22+
log = get_logger("bot")
2523
log.fatal("", exc_info=e.exception)
2624
log.fatal(message)
2725

bot/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import asyncio
2-
import logging
32
from typing import Optional
43
from urllib.parse import quote as quote_url
54

65
import aiohttp
76

7+
from bot.log import get_logger
8+
89
from .constants import Keys, URLs
910

10-
log = logging.getLogger(__name__)
11+
log = get_logger(__name__)
1112

1213

1314
class ResponseCodeError(ValueError):

bot/async_stats.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from statsd.client.base import StatsClientBase
55

6+
from bot.utils import scheduling
7+
68

79
class AsyncStatsClient(StatsClientBase):
810
"""An async transport method for statsd communication."""
@@ -32,7 +34,7 @@ async def create_socket(self) -> None:
3234

3335
def _send(self, data: str) -> None:
3436
"""Start an async task to send data to statsd."""
35-
self._loop.create_task(self._async_send(data))
37+
scheduling.create_task(self._async_send(data), event_loop=self._loop)
3638

3739
async def _async_send(self, data: str) -> None:
3840
"""Send data to the statsd server using the async transport."""

bot/bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import asyncio
2-
import logging
32
import socket
43
import warnings
54
from collections import defaultdict
@@ -14,8 +13,9 @@
1413

1514
from bot import api, constants
1615
from bot.async_stats import AsyncStatsClient
16+
from bot.log import get_logger
1717

18-
log = logging.getLogger('bot')
18+
log = get_logger('bot')
1919
LOCALHOST = "127.0.0.1"
2020

2121

bot/command.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

bot/constants.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
out in the custom user configuration will stay
1010
their default values from `config-default.yml`.
1111
"""
12-
13-
import logging
1412
import os
1513
from collections.abc import Mapping
1614
from enum import Enum
@@ -25,8 +23,6 @@
2523
except ModuleNotFoundError:
2624
pass
2725

28-
log = logging.getLogger(__name__)
29-
3026

3127
def _env_var_constructor(loader, node):
3228
"""
@@ -104,7 +100,7 @@ def _recursive_update(original, new):
104100

105101

106102
if Path("config.yml").exists():
107-
log.info("Found `config.yml` file, loading constants from it.")
103+
print("Found `config.yml` file, loading constants from it.")
108104
with open("config.yml", encoding="UTF-8") as f:
109105
user_config = yaml.safe_load(f)
110106
_recursive_update(_CONFIG_YAML, user_config)
@@ -123,11 +119,10 @@ def check_required_keys(keys):
123119
if lookup is None:
124120
raise KeyError(key)
125121
except KeyError:
126-
log.critical(
122+
raise KeyError(
127123
f"A configuration for `{key_path}` is required, but was not found. "
128124
"Please set it in `config.yml` or setup an environment variable and try again."
129125
)
130-
raise
131126

132127

133128
try:
@@ -186,8 +181,7 @@ def __getattr__(cls, name):
186181
(cls.section, cls.subsection, name)
187182
if cls.subsection is not None else (cls.section, name)
188183
)
189-
# Only an INFO log since this can be caught through `hasattr` or `getattr`.
190-
log.info(f"Tried accessing configuration variable at `{dotted_path}`, but it could not be found.")
184+
print(f"Tried accessing configuration variable at `{dotted_path}`, but it could not be found.")
191185
raise AttributeError(repr(name)) from e
192186

193187
def __getitem__(cls, name):

bot/converters.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import logging
43
import re
54
import typing as t
65
from datetime import datetime
@@ -19,13 +18,15 @@
1918
from bot.constants import URLs
2019
from bot.errors import InvalidInfraction
2120
from bot.exts.info.doc import _inventory_parser
21+
from bot.log import get_logger
2222
from bot.utils.extensions import EXTENSIONS, unqualify
2323
from bot.utils.regex import INVITE_RE
2424
from bot.utils.time import parse_duration_string
25+
2526
if t.TYPE_CHECKING:
2627
from bot.exts.info.source import SourceType
2728

28-
log = logging.getLogger(__name__)
29+
log = get_logger(__name__)
2930

3031
DISCORD_EPOCH_DT = datetime.utcfromtimestamp(DISCORD_EPOCH / 1000)
3132
RE_USER_MENTION = re.compile(r"<@!?([0-9]+)>$")
@@ -70,10 +71,10 @@ class ValidDiscordServerInvite(Converter):
7071

7172
async def convert(self, ctx: Context, server_invite: str) -> dict:
7273
"""Check whether the string is a valid Discord server invite."""
73-
invite_code = INVITE_RE.search(server_invite)
74+
invite_code = INVITE_RE.match(server_invite)
7475
if invite_code:
7576
response = await ctx.bot.http_session.get(
76-
f"{URLs.discord_invite_api}/{invite_code[1]}"
77+
f"{URLs.discord_invite_api}/{invite_code.group('invite')}"
7778
)
7879
if response.status != 404:
7980
invite_data = await response.json()
@@ -235,11 +236,16 @@ class Inventory(Converter):
235236
async def convert(ctx: Context, url: str) -> t.Tuple[str, _inventory_parser.InventoryDict]:
236237
"""Convert url to Intersphinx inventory URL."""
237238
await ctx.trigger_typing()
238-
if (inventory := await _inventory_parser.fetch_inventory(url)) is None:
239-
raise BadArgument(
240-
f"Failed to fetch inventory file after {_inventory_parser.FAILED_REQUEST_ATTEMPTS} attempts."
241-
)
242-
return url, inventory
239+
try:
240+
inventory = await _inventory_parser.fetch_inventory(url)
241+
except _inventory_parser.InvalidHeaderError:
242+
raise BadArgument("Unable to parse inventory because of invalid header, check if URL is correct.")
243+
else:
244+
if inventory is None:
245+
raise BadArgument(
246+
f"Failed to fetch inventory file after {_inventory_parser.FAILED_REQUEST_ATTEMPTS} attempts."
247+
)
248+
return url, inventory
243249

244250

245251
class Snowflake(IDConverter):
@@ -267,7 +273,7 @@ async def convert(self, ctx: Context, arg: str) -> int:
267273
snowflake = int(arg)
268274

269275
try:
270-
time = snowflake_time(snowflake)
276+
time = snowflake_time(snowflake).replace(tzinfo=None)
271277
except (OverflowError, OSError) as e:
272278
# Not sure if this can ever even happen, but let's be safe.
273279
raise BadArgument(f"{error}: {e}")
@@ -392,7 +398,8 @@ async def convert(self, ctx: Context, duration: str) -> datetime:
392398
class OffTopicName(Converter):
393399
"""A converter that ensures an added off-topic name is valid."""
394400

395-
ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-"
401+
ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!?'`-<>"
402+
TRANSLATED_CHARACTERS = "𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹ǃ?’’-<>"
396403

397404
@classmethod
398405
def translate_name(cls, name: str, *, from_unicode: bool = True) -> str:
@@ -402,9 +409,9 @@ def translate_name(cls, name: str, *, from_unicode: bool = True) -> str:
402409
If `from_unicode` is True, the name is translated from a discord-safe format, back to normalized text.
403410
"""
404411
if from_unicode:
405-
table = str.maketrans(cls.ALLOWED_CHARACTERS, '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹ǃ?’’-')
412+
table = str.maketrans(cls.ALLOWED_CHARACTERS, cls.TRANSLATED_CHARACTERS)
406413
else:
407-
table = str.maketrans('𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹ǃ?’’-', cls.ALLOWED_CHARACTERS)
414+
table = str.maketrans(cls.TRANSLATED_CHARACTERS, cls.ALLOWED_CHARACTERS)
408415

409416
return name.translate(table)
410417

0 commit comments

Comments
 (0)