diff --git a/discord/enums.py b/discord/enums.py index eaf8aef5e..6b47d17b8 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -74,6 +74,8 @@ __all__ = ( 'EntitlementType', 'EntitlementOwnerType', 'PollLayoutType', + 'ScheduledEventRecurrenceFrequency', + 'ScheduledEventRecurrenceWeekday', ) @@ -835,6 +837,23 @@ class ReactionType(Enum): burst = 1 +class ScheduledEventRecurrenceFrequency(Enum): + yearly = 0 + monthly = 1 + weekly = 2 + daily = 3 + + +class ScheduledEventRecurrenceWeekday(Enum): + monday = 0 + tuesday = 1 + wednesday = 2 + thursday = 3 + friday = 4 + saturday = 5 + sunday = 6 + + def create_unknown_value(cls: Type[E], val: Any) -> E: value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below name = f'unknown_{val}' diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 9717fa0ce..15f8874d7 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -24,6 +24,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations +import re from datetime import datetime, date from typing import ( TYPE_CHECKING, @@ -42,6 +43,8 @@ from .enums import ( EventStatus, EntityType, PrivacyLevel, + ScheduledEventRecurrenceFrequency, + ScheduledEventRecurrenceWeekday, try_enum ) from .mixins import Hashable @@ -74,10 +77,19 @@ __all__ = ( ) # fmt: on +_RFC5545_PATTERN = re.compile( + r"""^DTSTART:(?P(?P\d{8)(T(?P\d{6}Z)?)?)(;TZID=(?P[A-Za-z0-9/_-]+))?;RRULE: +FREQ=(?PDAILY|WEEKLY|MONTHLY|YEARLY)(;INTERVAL=(?P\d+))?(;COUNT=(?P\d+)|;UNTIL=(?P +\d{8}T\d{6}Z))?(;BYDAY=(?P(MO|TU|WE|TH|FR|SA|SU)(,(MO|TU|WE|TH|FR|SA|SU))*))?(;BYMONTHDAY(?P(-?\d{1,2 +})(,-?\d{1,2})*))?(;BYYEARDAY=(?P(-?\d{1,3})(,-?\d{1,3})*))?(;BYWEEKNO=(?P(-?\d{1,2})(,-?\d{1,2})*) +)?(;BYMONTH=(?P\d{1,2}(,\d{1,2})*))?(;BYSETPOS=(?P(-?\d+)(,-?\d+)*))?(;WKST=(?P(SU|MO|TU|WE|TH| +FR|SA)))?$ +""" +) class _NWeekday(NamedTuple): week: Literal[1, 2, 3, 4, 5] - day: Literal[0, 1, 2, 3, 4, 5, 6] + day: ScheduledEventRecurrenceWeekday class ScheduledEventRecurrenceRule: @@ -89,34 +101,16 @@ class ScheduledEventRecurrenceRule: ---------- start: :class:`datetime.datetime` The datetime when the recurrence interval starts. - frequency: :class:`int` + frequency: :class:`ScheduledEventRecurrenceFrequency` How often the event occurs. - - The following table represents the available frequency values: - - +---------------+---------------+ - | Value | Type | - +===============+===============+ - | ``0`` | yearly | - +---------------+---------------+ - | ``1`` | monthly | - +---------------+---------------+ - | ``2`` | weekly | - +---------------+---------------+ - | ``3`` | daily | - +---------------+---------------+ interval: :class:`int` The spacing between the events, defined by ``frequency``. - For example, a ``frequency`` of ``2`` (weekly) and an ``interval`` of ``2`` will result in - a "Every other week" recurrence rule. - weekdays: Optional[List[:class:`int`]] + For example, a ``frequency`` of :attr:`ScheduledEventRecurrenceFrequency.weekly` and an ``interval`` of ``2`` + will result in an "Every other week" recurrence rule. + weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] The weekdays the event will recur on. - - To prevent value errors use the ``calendar`` module with the available weekdays constants: - :attr:`calendar.MONDAY`, :attr:`calendar.TUESDAY`, :attr:`calendar.WEDNESDAY`, :attr:`calendar.THURSDAY`, - :attr:`calendar.FRIDAY`, :attr:`calendar.SATURDAY`, and :attr:`calendar.SUNDAY`. - n_weekdays: Optional[List[Tuple[:class:`int`, :class:`int`]]] + n_weekdays: Optional[List[Tuple[:class:`int`, :class:`ScheduledEVentRecurrenceWeekday`]]] A (week, weekday) tuple list of the N weekdays the event will recur on. month_days: Optional[List[:class:`datetime.date`]] The months and month days the scheduled event will recur on. @@ -137,30 +131,69 @@ class ScheduledEventRecurrenceRule: '_year_days', ) + @overload def __init__( self, /, start: datetime, - frequency: Literal[0, 1, 2, 3,], + frequency: ScheduledEventRecurrenceFrequency, interval: int, *, - weekdays: Optional[List[int]] = MISSING, + weekdays: Optional[List[ScheduledEventRecurrenceWeekday]], + ) -> None: + ... + + @overload + def __init__( + self, + /, + start: datetime, + frequency: ScheduledEventRecurrenceFrequency, + interval: int, + *, + n_weekdays: Optional[List[_NWeekday]], + ) -> None: + ... + + @overload + def __init__( + self, + /, + start: datetime, + frequency: ScheduledEventRecurrenceFrequency, + interval: int, + *, + month_days: Optional[List[date]], + ) -> None: + ... + + def __init__( + self, + /, + start: datetime, + frequency: ScheduledEventRecurrenceFrequency, + interval: int, + *, + weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = MISSING, n_weekdays: Optional[List[_NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> None: self.start: datetime = start - self.frequency: Literal[0, 1, 2, 3,] = frequency + self.frequency: ScheduledEventRecurrenceFrequency = frequency self.interval: int = interval self._count: Optional[int] = None self._end: Optional[datetime] = None self._year_days: Optional[List[int]] = None - self._weekdays: Optional[List[int]] = weekdays + self._weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = weekdays self._n_weekdays: Optional[List[_NWeekday]] = n_weekdays self._month_days: Optional[List[date]] = month_days + def __repr__(self) -> str: + return f'' + @property - def weekdays(self) -> Optional[List[int]]: - """Optional[List[:class:`int`]]: Returns a read-only list of the weekdays this event + def weekdays(self) -> Optional[List[ScheduledEventRecurrenceWeekday]]: + """Optional[List[:class:`ScheduledEventRecurrenceWeekday`]]: Returns a read-only list of the weekdays this event recurs on, or ``None``. """ if self._weekdays in (MISSING, None): @@ -168,12 +201,12 @@ class ScheduledEventRecurrenceRule: return self._weekdays.copy() @weekdays.setter - def weekdays(self, new: Optional[List[int]]) -> None: + def weekdays(self, new: Optional[List[ScheduledEventRecurrenceWeekday]]) -> None: self._weekdays = new @property def n_weekdays(self) -> Optional[List[_NWeekday]]: - """Optional[List[Tuple[:class:`int`, :class:`int`]]]: Returns a read-only + """Optional[List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]]: Returns a read-only list of the N weekdays this event recurs on, or ``None``. """ if self._n_weekdays in (MISSING, None): @@ -209,6 +242,7 @@ class ScheduledEventRecurrenceRule: """Optional[:class:`int`]: The amount of times the event will recur before stopping, or ``None`` if it recurs forever. """ + return self._count @property def year_days(self) -> Optional[List[int]]: @@ -222,7 +256,7 @@ class ScheduledEventRecurrenceRule: def replace( self, *, - weekdays: Optional[List[int]] = MISSING, + weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = MISSING, n_weekdays: Optional[List[_NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> Self: @@ -231,9 +265,9 @@ class ScheduledEventRecurrenceRule: Parameters ---------- - weekdays: Optional[List[:class:`int`]] + weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] The new weekdays for the event to recur on. - n_weekdays: Optional[List[Tuple[:class:`int`, :class:`int`]]] + n_weekdays: Optional[List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]] The new set of specific days within a week for the event to recur on. month_days: Optional[List[:class:`datetime.date`]] The new set of month and month days for the event to recur on. @@ -305,12 +339,35 @@ class ScheduledEventRecurrenceRule: self._count = data.get('count') self._year_days = data.get('by_year_day') + end = data.get('end') + if end is not None: + self._end = parse_time(end) + end = data.get('end') if end is not None: self._end = parse_time(end) # TODO: finish this impl return self + @classmethod + def from_str(cls, string: str, /) -> Self: + """Constructs a recurrence rule from a RFC5545 rrule string, + + Parameters + ---------- + string: :class:`str` + The string to construct the recurrence rule from. + + Returns + ------- + :class:`.ScheduledEventRecurrenceRule` + The recurrence rule. + """ + + match = _RFC5545_PATTERN.match(string) + + # TODO: finish this impl + class ScheduledEvent(Hashable): """Represents a scheduled event in a guild. @@ -361,7 +418,7 @@ class ScheduledEvent(Hashable): .. versionadded:: 2.2 location: Optional[:class:`str`] The location of the scheduled event. - recurrence_rule: Optional[:class:`ScheduledEventRecurrence`] + recurrence_rule: Optional[:class:`.ScheduledEventRecurrence`] The recurrence rule for this event, or ``None``. .. versionadded:: 2.5 @@ -408,11 +465,8 @@ class ScheduledEvent(Hashable): self.user_count: int = data.get('user_count', 0) self.creator_id: Optional[int] = _get_as_snowflake(data, 'creator_id') - self.recurrence_rule: Optional[ScheduledEventRecurrenceRule] = None - recurrence_rule_data = data.get('recurrence_rule') - - if recurrence_rule_data is not None: - self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(recurrence_rule_data) + recurrence_rule = data.get('recurrence_rule') + self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(recurrence_rule) if recurrence_rule else None creator = data.get('creator') self.creator: Optional[User] = self._state.store_user(creator) if creator else None @@ -715,7 +769,7 @@ class ScheduledEvent(Hashable): Required if the entity type is :attr:`EntityType.external`. reason: Optional[:class:`str`] The reason for editing the scheduled event. Shows up on the audit log. - recurrence: Optional[:class:`ScheduledEventRecurrence`] + recurrence_rule: Optional[:class:`.ScheduledEventRecurrence`] The recurrence rule this event will follow, or `None` to set it to a one-time event.