from __future__ import annotations import argparse import dataclasses from datetime import datetime, timedelta, timezone from typing import NamedTuple, Optional, Tuple from redbot.core.commands import BadArgument, Context from .time_utils import parse_time, parse_timedelta class NonNumeric(NamedTuple): parsed: str @classmethod async def convert(cls, context: Context, argument: str): if argument.isdigit(): raise BadArgument("Event names must contain at least 1 non-numeric value") return cls(argument) class NoExitParser(argparse.ArgumentParser): def error(self, message): raise BadArgument() @dataclasses.dataclass() class Schedule: start: datetime command: str recur: Optional[timedelta] = None quiet: bool = False def to_tuple(self) -> Tuple[str, datetime, Optional[timedelta]]: return self.command, self.start, self.recur @classmethod async def convert(cls, ctx: Context, argument: str): start: datetime command: Optional[str] = None recur: Optional[timedelta] = None command, *arguments = argument.split(" -- ") if arguments: argument = " -- ".join(arguments) else: command = None parser = NoExitParser(description="Scheduler event parsing", add_help=False) parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False) parser.add_argument("--every", nargs="*", dest="every", default=[]) if not command: parser.add_argument("command", nargs="*") at_or_in = parser.add_mutually_exclusive_group() at_or_in.add_argument("--start-at", nargs="*", dest="at", default=[]) at_or_in.add_argument("--start-in", nargs="*", dest="in", default=[]) try: vals = vars(parser.parse_args(argument.split(" "))) except Exception as exc: raise BadArgument() from exc if not (vals["at"] or vals["in"]): raise BadArgument("You must provide one of `--start-in` or `--start-at`") if not command and not vals["command"]: raise BadArgument("You have to provide a command to run") command = command or " ".join(vals["command"]) for delta in ("in", "every"): if vals[delta]: parsed = parse_timedelta(" ".join(vals[delta])) if not parsed: raise BadArgument("I couldn't understand that time interval") if delta == "in": start = datetime.now(timezone.utc) + parsed else: recur = parsed if recur.total_seconds() < 60: raise BadArgument( "You can't schedule something to happen that frequently, " "I'll get ratelimited." ) if vals["at"]: try: start = parse_time(" ".join(vals["at"])) except Exception: raise BadArgument("I couldn't understand that starting time.") from None return cls(command=command, start=start, recur=recur, quiet=vals["quiet"]) class TempMute(NamedTuple): reason: Optional[str] start: datetime @classmethod async def convert(cls, ctx: Context, argument: str): start: datetime reason: str parser = NoExitParser(description="Scheduler event parsing", add_help=False) parser.add_argument("reason", nargs="*") at_or_in = parser.add_mutually_exclusive_group() at_or_in.add_argument("--until", nargs="*", dest="until", default=[]) at_or_in.add_argument("--for", nargs="*", dest="for", default=[]) try: vals = vars(parser.parse_args(argument.split())) except Exception as exc: raise BadArgument() from exc if not (vals["until"] or vals["for"]): raise BadArgument("You must provide one of `--until` or `--for`") reason = " ".join(vals["reason"]) if vals["for"]: parsed = parse_timedelta(" ".join(vals["for"])) if not parsed: raise BadArgument("I couldn't understand that time interval") start = datetime.now(timezone.utc) + parsed if vals["until"]: try: start = parse_time(" ".join(vals["at"])) except Exception: raise BadArgument("I couldn't understand that unmute time.") from None return cls(reason, start)