forked from Cog-Creators/Red-DiscordBot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve timedelta conversions (Cog-Creators#6349)
Co-authored-by: zephyrkul <zephyrkul@users.noreply.github.com>
- Loading branch information
Showing
2 changed files
with
65 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,76 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
import re | ||
from typing import Union, Dict | ||
from typing import Optional, TypedDict | ||
from datetime import timedelta | ||
from typing_extensions import Annotated | ||
|
||
from discord.ext.commands.converter import Converter | ||
from redbot.core import commands | ||
from redbot.core import i18n | ||
from redbot.core.commands.converter import TIME_RE | ||
|
||
_ = i18n.Translator("Mutes", __file__) | ||
log = logging.getLogger("red.cogs.mutes") | ||
|
||
# the following regex is slightly modified from Red | ||
# it's changed to be slightly more strict on matching with finditer | ||
# this is to prevent "empty" matches when parsing the full reason | ||
# This is also designed more to allow time interval at the beginning or the end of the mute | ||
# to account for those times when you think of adding time *after* already typing out the reason | ||
# https://github.com/Cog-Creators/Red-DiscordBot/blob/V3/develop/redbot/core/commands/converter.py#L55 | ||
TIME_RE_STRING = r"|".join( | ||
[ | ||
r"((?P<weeks>\d+?)\s?(weeks?|w))", | ||
r"((?P<days>\d+?)\s?(days?|d))", | ||
r"((?P<hours>\d+?)\s?(hours?|hrs|hr?))", | ||
r"((?P<minutes>\d+?)\s?(minutes?|mins?|m(?!o)))", # prevent matching "months" | ||
r"((?P<seconds>\d+?)\s?(seconds?|secs?|s))", | ||
] | ||
) | ||
TIME_RE = re.compile(TIME_RE_STRING, re.I) | ||
TIME_SPLIT = re.compile(r"t(?:ime)?=") | ||
TIME_SPLIT = re.compile(r"t(?:ime\s?)?=\s*") | ||
|
||
_ = i18n.Translator("Mutes", __file__) | ||
|
||
def _edgematch(pattern: re.Pattern[str], argument: str) -> Optional[re.Match[str]]: | ||
"""Internal utility to match at either end of the argument string""" | ||
# precondition: pattern does not end in $ | ||
# precondition: argument does not end in whitespace | ||
return pattern.match(argument) or re.search( | ||
pattern.pattern + "$", argument, flags=pattern.flags | ||
) | ||
|
||
|
||
class MuteTime(Converter): | ||
class _MuteTime(TypedDict, total=False): | ||
duration: timedelta | ||
reason: str | ||
|
||
|
||
class _MuteTimeConverter(Converter): | ||
""" | ||
This will parse my defined multi response pattern and provide usable formats | ||
to be used in multiple responses | ||
""" | ||
|
||
async def convert( | ||
self, ctx: commands.Context, argument: str | ||
) -> Dict[str, Union[timedelta, str, None]]: | ||
time_split = TIME_SPLIT.split(argument) | ||
result: Dict[str, Union[timedelta, str, None]] = {} | ||
async def convert(self, ctx: commands.Context, argument: str) -> _MuteTime: | ||
time_split = TIME_SPLIT.search(argument) | ||
result: _MuteTime = {} | ||
if time_split: | ||
maybe_time = time_split[-1] | ||
maybe_time = argument[time_split.end() :] | ||
strategy = re.match | ||
else: | ||
maybe_time = argument | ||
strategy = _edgematch | ||
|
||
time_data = {} | ||
for time in TIME_RE.finditer(maybe_time): | ||
argument = argument.replace(time[0], "") | ||
for k, v in time.groupdict().items(): | ||
if v: | ||
time_data[k] = int(v) | ||
if time_data: | ||
match = strategy(TIME_RE, maybe_time) | ||
if match: | ||
time_data = {k: int(v) for k, v in match.groupdict().items() if v is not None} | ||
for k in time_data: | ||
if k in ("years", "months"): | ||
raise commands.BadArgument( | ||
_("`{unit}` is not a valid unit of time for this command").format(unit=k) | ||
) | ||
try: | ||
result["duration"] = timedelta(**time_data) | ||
result["duration"] = duration = timedelta(**time_data) | ||
except OverflowError: | ||
raise commands.BadArgument( | ||
_("The time provided is too long; use a more reasonable time.") | ||
) | ||
if duration <= timedelta(seconds=0): | ||
raise commands.BadArgument(_("The time provided must not be in the past.")) | ||
if time_split: | ||
start, end = time_split.span() | ||
end += match.end() | ||
else: | ||
start, end = match.span() | ||
argument = argument[:start] + argument[end:] | ||
result["reason"] = argument.strip() | ||
return result | ||
|
||
|
||
MuteTime = Annotated[_MuteTime, _MuteTimeConverter] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters