From 41706ddb277bc81bd7e883737a0b0f61d0fb3b97 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 14 Dec 2023 21:23:40 +0100 Subject: [PATCH 01/46] Base commit --- discord/guild.py | 13 +- discord/http.py | 23 +++ discord/scheduled_event.py | 269 ++++++++++++++++++++++++++++++- discord/types/scheduled_event.py | 29 +++- 4 files changed, 330 insertions(+), 4 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 82692ff73388..2058f5e222ae 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -83,7 +83,7 @@ from .asset import Asset from .flags import SystemChannelFlags from .integrations import Integration, PartialIntegration, _integration_factory -from .scheduled_event import ScheduledEvent +from .scheduled_event import ScheduledEvent, ScheduledEventRecurrence from .stage_instance import StageInstance from .threads import Thread, ThreadMember from .sticker import GuildSticker @@ -3003,6 +3003,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -3019,6 +3020,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -3034,6 +3036,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -3049,6 +3052,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -3065,6 +3069,7 @@ async def create_scheduled_event( description: str = MISSING, image: bytes = MISSING, reason: Optional[str] = None, + recurrence: Optional[ScheduledEventRecurrence] = MISSING, ) -> ScheduledEvent: r"""|coro| @@ -3111,6 +3116,9 @@ async def create_scheduled_event( Required if the ``entity_type`` is :attr:`EntityType.external`. reason: Optional[:class:`str`] The reason for creating this scheduled event. Shows up on the audit log. + recurrence: Optional[:class:`ScheduledEventRecurrence`] + The recurrence rule this event will follow. If this is `None` then this is + a one-time event. Raises ------- @@ -3205,6 +3213,9 @@ async def create_scheduled_event( ) payload['scheduled_end_time'] = end_time.isoformat() + if recurrence not in (MISSING, None): + payload['recurrence_rule'] = recurrence.to_dict() + if metadata: payload['entity_metadata'] = metadata diff --git a/discord/http.py b/discord/http.py index 409a36cc98e0..e032e0eaa416 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1985,6 +1985,7 @@ def create_guild_scheduled_event( 'description', 'entity_type', 'image', + 'recurrence_rule' ) payload = {k: v for k, v in payload.items() if k in valid_keys} @@ -2038,6 +2039,7 @@ def edit_scheduled_event( 'description', 'entity_type', 'image', + 'recurrence_rule' ) payload = {k: v for k, v in payload.items() if k in valid_keys} @@ -2133,6 +2135,27 @@ def get_scheduled_event_users( ), params=params, ) + + def get_scheduled_event_counts( + self, + guild_id: Snowflake, + guild_scheduled_event_id: Snowflake, + scheduled_event_exception_ids: Tuple[Snowflake, ...] + ) -> Response[scheduled_event.GuildScheduledEventExceptionCounts]: + route: str = '/guilds/{guild_id}/scheduled-events/{guild_scheduled_event_id}/users/counts?' + + if len(scheduled_event_exception_ids) > 0: + for exception_id in scheduled_event_exception_ids: + route += f"guild_scheduled_event_exception_ids={exception_id}&" + + return self.request( + Route( + 'GET', + route, + guild_id=guild_id, + guild_scheduled_event_id=guild_scheduled_event_id + ) + ) # Application commands (global) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index f74ae67061e5..a469b557efca 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -25,7 +25,7 @@ from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union, overload, Literal +from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union, overload, Literal, List, Tuple from .asset import Asset from .enums import EventStatus, EntityType, PrivacyLevel, try_enum @@ -37,6 +37,8 @@ from .types.scheduled_event import ( GuildScheduledEvent as BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCount as GuildScheduledEventWithUserCountPayload, + GuildScheduledEventRecurrence as GuildScheduledEventRecurrencePayload, + GuildScheduledEventExceptionCounts as GuildScheduledEventExceptionCountsPayload, EntityMetadata, ) @@ -51,10 +53,220 @@ # fmt: off __all__ = ( "ScheduledEvent", + "ScheduledEventRecurrence", + "ScheduledEventExceptionCount" ) # fmt: on +class ScheduledEventExceptionCount: + """Represents the exception counts in a Scheduled Event. + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two Exception Counts are equal. + """ + + def __init__(self, data: GuildScheduledEventExceptionCountsPayload) -> None: + self.count: int = int(data.get('guild_scheduled_event_count')) + + self._exception_snowflakes: Dict[Union[str, int], int] = data.get('guild_scheduled_event_exception_counts') + + @property + def exception_ids(self) -> List[int]: + """List[:class:`int`]: A list containing all the exception event IDs""" + return [int(id) for id in self._exception_snowflakes.keys()] + + @property + def exceptions(self) -> Dict[int, int]: + """Dict[:class:`int`, :class:`int`]: A dictionary containing all the + event IDs as keys and their respective exception counts as value. + """ + + return {int(snowflake): count for snowflake, count in self._exception_snowflakes.items()} + + +class ScheduledEventRecurrence: + """Represents a Scheduled Event Recurrence + + .. versionadded:: 2.4 + + .. container:: operations + + .. describe:: x == y + + Checks if two Scheduled Event Recurrences are equal + + Parameters + ---------- + start: :class:`datetime.datetime` + When the first event of this series is started. + end: Optional[:class:`datetime.datetime`] + When the events of this series will stop. If it is `None`, it will repeat forever. + weekday: :class:`int` + An integer representing the weekday this event will repeat in. Monday is 0 + and Sunday is 6. + n_weekday: Tuple[:class:`int`, :class:`int`] + A tuple that contain the N weekday this event will repeat in. + + For example, if you want for this event to repeat the 1st Monday of the month, + then this param should have a value of `(1, 0)`. Where ``1`` represents the + 'first' and ``0`` the weekday, in this case, Monday. + month: :class:`int` + An integer representing the month this event will repeat in. + month_days: List[:class:`int`] + A list of integers representing the month days this event will repeat in. + + This marks the days of the month this event will repeat in, for example, if it + is set to `1`, this event will repeat the first day of every month. + year_days: List[:class:`int`] + A list of integers representing the year days this event will repeat in. + + This marks the days of the year this event will repeat in, for example, if it + is set to `1`, this event will repeat the first day of every year. + """ + + @overload + def __init__( + self, + start: datetime, + *, + weekdays: List[Literal[0, 1, 2, 3, 4, 5, 6]], + end: Optional[datetime] = ..., + ) -> None: + ... + + @overload + def __init__( + self, + start: datetime, + *, + n_weekday: Tuple[Literal[1, 2, 3, 4], int], + end: Optional[datetime] = ..., + ) -> None: + ... + + @overload + def __init__( + self, + start: datetime, + *, + month: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + month_days: List[int], + end: Optional[datetime] = ..., + ) -> None: + ... + + @overload + def __init__( + self, + start: datetime, + *, + year_days: List[int], + end: Optional[datetime] = ..., + ) -> None: + ... + + def __init__( + self, + start: datetime, + *, + weekdays: List[Literal[0, 1, 2, 3, 4, 5, 6]] = MISSING, + n_weekday: Tuple[Literal[1, 2, 3, 4], int] = MISSING, + month: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] = MISSING, + month_days: List[int] = MISSING, + year_days: List[int] = MISSING, + end: Optional[datetime] = MISSING, + ) -> None: + + if not start.tzinfo: + raise ValueError( + '\'start\' must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ) + + if end not in (MISSING, None): + if not end.tzinfo: + raise ValueError( + '\'end\' must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ) + + self.start: datetime = start + self.end: Optional[datetime] = end if end is not MISSING else None + + self.weekdays: Optional[List[int]] = weekdays if weekdays is not MISSING else None + self.n_weekday: Optional[Tuple[int, int]] = n_weekday if n_weekday is not MISSING else None + self.month: Optional[int] = month if month is not MISSING else None + self.month_days: Optional[List[int]] = month_days if month_days is not MISSING else None + self.year_days: Optional[List[int]] = year_days if year_days is not MISSING else None + self._interval: int = 1 + + def __eq__(self, other: object) -> bool: + if isinstance(other, self.__class__): + return ( + self.start == other.start + ) + return NotImplemented + + def __set_interval(self, value: int) -> None: + # Inner function to set the interval to the one that we + # recieved from the API + self._interval: int = value + + @property + def frequency(self) -> int: + """:class:`int`: Returns the frequency of this recurrent scheduled event""" + + # This is now an internal parameter because if it is user-provided this could cause + # HTTPExceptions when creating or editing events. + + if self.weekdays is not None: + return 2 if len(self.weekdays) == 1 else 3 + elif self.n_weekday is not None: + return 1 + elif self.month is not None and self.month_days is not None: + return 0 + return 0 # In case None of the cases matches (i.e.: year_days) then we return 0 + + @property + def interval(self) -> int: + """:class:`int`: Returns the interval of this recurrent scheduled event""" + return self._interval + + def to_dict(self) -> GuildScheduledEventRecurrencePayload: + return { + "start": self.start.isoformat(), + "end": self.end.isoformat() if self.end else None, + "by_weekday": self.weekdays or [], + "by_month": [self.month,] if self.month else [], + "by_month_day": self.month_days or [], + "by_n_weekday": [self.n_weekday,] if self.n_weekday else [], + "by_year_day": self.year_days or [], + "count": None, # There isn't counts, yet + "frequency": self.frequency, + "interval": self.interval, + } # type: ignore + + @classmethod + def from_dict(cls, data: GuildScheduledEventRecurrencePayload) -> ScheduledEventRecurrence: + self: cls = cls( + start=datetime.fromisoformat(data.get('start')), + weekdays=data.get('by_weekday', MISSING), + n_weekdays=((d['n'], d['day']) for d in data.get('by_n_weekday')) if data.get('by_n_weekday', MISSING) is not MISSING else MISSING, + month=data.get('by_month')[0] if len(data.get('by_month', [])) > 0 and data.get('by_month', MISSING) is not MISSING else MISSING, + month_days=data.get('by_month_day', MISSING), + year_days=data.get('by_year_day', MISSING), + end=data.get('end', MISSING) + ) # type: ignore + + self.__set_interval(int(data.get('interval', 1))) + + return self + + class ScheduledEvent(Hashable): """Represents a scheduled event in a guild. @@ -104,6 +316,10 @@ class ScheduledEvent(Hashable): .. versionadded:: 2.2 location: Optional[:class:`str`] The location of the scheduled event. + recurrence: Optional[:class:`ScheduledEventRecurrence`] + The recurrence rule this event follows, if any. + + .. versionadded:: 2.4 """ __slots__ = ( @@ -125,6 +341,7 @@ class ScheduledEvent(Hashable): 'channel_id', 'creator_id', 'location', + 'recurrence', ) def __init__(self, *, state: ConnectionState, data: GuildScheduledEventPayload) -> None: @@ -145,6 +362,7 @@ def _update(self, data: GuildScheduledEventPayload) -> None: self._cover_image: Optional[str] = data.get('image', None) self.user_count: int = data.get('user_count', 0) self.creator_id: Optional[int] = _get_as_snowflake(data, 'creator_id') + self.recurrence: Optional[ScheduledEventRecurrence] = ScheduledEventRecurrence.from_dict(data.get('recurrence_rule')) if data.get('recurrence_rule', None) is not None else None creator = data.get('creator') self.creator: Optional[User] = self._state.store_user(creator) if creator else None @@ -310,6 +528,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -327,6 +546,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -344,6 +564,7 @@ async def edit( image: bytes = ..., location: str, reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -360,6 +581,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -376,6 +598,7 @@ async def edit( image: bytes = ..., location: str, reason: Optional[str] = ..., + recurrence: Optional[ScheduledEventRecurrence] = ..., ) -> ScheduledEvent: ... @@ -393,6 +616,7 @@ async def edit( image: bytes = MISSING, location: str = MISSING, reason: Optional[str] = None, + recurrence: Optional[ScheduledEventRecurrence] = MISSING, ) -> ScheduledEvent: r"""|coro| @@ -441,6 +665,9 @@ async def edit( 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`] + The recurrence rule this event will follow, or `None` to set it to a + one-time event. Raises ------- @@ -551,6 +778,12 @@ async def edit( else: payload['scheduled_end_time'] = end_time + if recurrence is not MISSING: + if recurrence is not None: + payload['recurrence_rule'] = recurrence.to_dict() + else: + payload['recurrence_rule'] = None + if metadata: payload['entity_metadata'] = metadata @@ -675,6 +908,40 @@ async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Opti # There's no data left after this break + async def fetch_counts(self, *children: Snowflake) -> ScheduledEventExceptionCount: + """|coro| + + Retrieves all the counts for this Event children, if this event isn't + recurrent, then this will return `None`. + + This also contains the exceptions of this Scheduled event. + + .. versionadded:: 2.4 + + Parameters + ---------- + *children: :class:`Snowflake` + The snowflakes of the children to fetcht the counts of. + + Raises + ------ + HTTPException + Fetching the counts failed. + + Returns + ------- + Optional[:class:`ScheduledEventExceptionCount`] + The counts of this event, or `None` if this event isn't recurrent or + there isn't any exception. + """ + + if not self.recurrence: + return None + + data = await self._state.http.get_scheduled_event_counts(self.guild_id, self.id, tuple([child.id for child in children])) + + return ScheduledEventExceptionCount(data) + def _add_user(self, user: User) -> None: self._users[user.id] = user diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index 52200367f134..4d76c2ef6f27 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -22,17 +22,38 @@ DEALINGS IN THE SOFTWARE. """ -from typing import List, Literal, Optional, TypedDict, Union +from typing import List, Literal, Optional, TypedDict, Union, Dict from typing_extensions import NotRequired from .snowflake import Snowflake from .user import User from .member import Member from .channel import PrivacyLevel as PrivacyLevel - EventStatus = Literal[1, 2, 3, 4] EntityType = Literal[1, 2, 3] +class _NWeekday(TypedDict): + n: int + day: Literal[0, 1, 2, 3, 4, 5, 6] + +class GuildScheduledEventRecurrence(TypedDict): + start: str + end: Optional[str] + frequency: int + interval: int + by_weekday: Optional[List[Literal[0, 1, 2, 3, 4, 5, 6]]] # NOTE: 0 = monday; 6 = sunday + by_n_weekday: Optional[List[_NWeekday]] + by_month: Optional[List[Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]]] + by_month_day: Optional[List[int]] # NOTE: day range between 1 and 31 + by_year_day: Optional[List[int]] + count: Optional[int] # maybe? +# NOTE: for this ^ enum, it is recommended to use "calendar" module constants: MONDAY; TUESDAY; WEDNESDAY; etc +# as they follow these patterns and is a built-in module. + +class GuildScheduledEventExceptionCounts(TypedDict): + guild_scheduled_event_count: int + guild_scheduled_event_exception_counts: Dict[Snowflake, int] +# NOTE: This class doesn't represent any of the user counts or the 'count' param in recurrence class _BaseGuildScheduledEvent(TypedDict): id: Snowflake @@ -42,6 +63,10 @@ class _BaseGuildScheduledEvent(TypedDict): scheduled_start_time: str privacy_level: PrivacyLevel status: EventStatus + auto_start: bool + guild_scheduled_events_exceptions: List # Didn't found items in the list yet + recurrence_rule: Optional[GuildScheduledEventRecurrence] + sku_ids: List[Snowflake] creator_id: NotRequired[Optional[Snowflake]] description: NotRequired[Optional[str]] creator: NotRequired[User] From 15274505869d6a03414a57fae001c7f99c5f100e Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Sat, 16 Dec 2023 12:37:49 +0100 Subject: [PATCH 02/46] Added eq dunder method to Scheduled Event Exception Counts --- discord/scheduled_event.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index a469b557efca..61b83be5a97c 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -76,6 +76,11 @@ def __init__(self, data: GuildScheduledEventExceptionCountsPayload) -> None: self._exception_snowflakes: Dict[Union[str, int], int] = data.get('guild_scheduled_event_exception_counts') + def __eq__(self, other: object) -> bool: + if isinstance(other, self.__class__): + return self.exception_ids == other.exception_ids + return NotImplemented + @property def exception_ids(self) -> List[int]: """List[:class:`int`]: A list containing all the exception event IDs""" From 8df1460fc436d95dcfedc7c2997a54a6d954cbb6 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 16:13:35 +0200 Subject: [PATCH 03/46] Reworked everything :pensive: --- discord/scheduled_event.py | 254 +++++++++++++------------------------ 1 file changed, 88 insertions(+), 166 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 61b83be5a97c..9a10defd2e7d 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -25,7 +25,17 @@ from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING, AsyncIterator, Dict, Optional, Union, overload, Literal, List, Tuple +from typing import ( + TYPE_CHECKING, + AsyncIterator, + Dict, + Optional, + Union, + overload, + Literal, + List, + NamedTuple, +) from .asset import Asset from .enums import EventStatus, EntityType, PrivacyLevel, try_enum @@ -53,223 +63,135 @@ # fmt: off __all__ = ( "ScheduledEvent", - "ScheduledEventRecurrence", - "ScheduledEventExceptionCount" ) # fmt: on +class NWeekdays(NamedTuple): + n: int + day: int -class ScheduledEventExceptionCount: - """Represents the exception counts in a Scheduled Event. - - .. versionadded:: 2.4 - - .. container:: operations - - .. describe:: x == y - - Checks if two Exception Counts are equal. - """ - - def __init__(self, data: GuildScheduledEventExceptionCountsPayload) -> None: - self.count: int = int(data.get('guild_scheduled_event_count')) - - self._exception_snowflakes: Dict[Union[str, int], int] = data.get('guild_scheduled_event_exception_counts') - - def __eq__(self, other: object) -> bool: - if isinstance(other, self.__class__): - return self.exception_ids == other.exception_ids - return NotImplemented - - @property - def exception_ids(self) -> List[int]: - """List[:class:`int`]: A list containing all the exception event IDs""" - return [int(id) for id in self._exception_snowflakes.keys()] - - @property - def exceptions(self) -> Dict[int, int]: - """Dict[:class:`int`, :class:`int`]: A dictionary containing all the - event IDs as keys and their respective exception counts as value. - """ - return {int(snowflake): count for snowflake, count in self._exception_snowflakes.items()} +class ScheduledEventRecurrenceRule: + """Represents a scheduled event recurrence rule. - -class ScheduledEventRecurrence: - """Represents a Scheduled Event Recurrence - .. versionadded:: 2.4 - .. container:: operations - - .. describe:: x == y - - Checks if two Scheduled Event Recurrences are equal - Parameters ---------- start: :class:`datetime.datetime` - When the first event of this series is started. - end: Optional[:class:`datetime.datetime`] - When the events of this series will stop. If it is `None`, it will repeat forever. - weekday: :class:`int` - An integer representing the weekday this event will repeat in. Monday is 0 - and Sunday is 6. - n_weekday: Tuple[:class:`int`, :class:`int`] - A tuple that contain the N weekday this event will repeat in. - - For example, if you want for this event to repeat the 1st Monday of the month, - then this param should have a value of `(1, 0)`. Where ``1`` represents the - 'first' and ``0`` the weekday, in this case, Monday. - month: :class:`int` - An integer representing the month this event will repeat in. + An aware datetime object representing the recurrence start time. + months: List[:class:`int`] + The months this event will be repeated on. month_days: List[:class:`int`] - A list of integers representing the month days this event will repeat in. - - This marks the days of the month this event will repeat in, for example, if it - is set to `1`, this event will repeat the first day of every month. + The month days this event will be repeated on. + weekdays: List[:class:`int`] + The weekdays this event will be reapeated on. year_days: List[:class:`int`] - A list of integers representing the year days this event will repeat in. - - This marks the days of the year this event will repeat in, for example, if it - is set to `1`, this event will repeat the first day of every year. + The year days this event will be repeated on. + n_weekdays: List[Tuple[:class:`int`, :class:`int`]] + A ``(day, month)`` tuple that represents the N day of a month that this event + will be repeated on. """ @overload def __init__( self, - start: datetime, *, - weekdays: List[Literal[0, 1, 2, 3, 4, 5, 6]], - end: Optional[datetime] = ..., + start: datetime, + months: List[int], + month_days: List[int], ) -> None: ... @overload def __init__( self, - start: datetime, *, - n_weekday: Tuple[Literal[1, 2, 3, 4], int], - end: Optional[datetime] = ..., + start: datetime, + weekdays: List[int], ) -> None: ... - + @overload def __init__( self, - start: datetime, *, - month: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], - month_days: List[int], - end: Optional[datetime] = ..., + start: datetime, + year_days: List[int], ) -> None: ... @overload def __init__( self, - start: datetime, *, - year_days: List[int], - end: Optional[datetime] = ..., + start: datetime, + n_weekdays: List[NWeekdays], ) -> None: ... def __init__( self, - start: datetime, *, - weekdays: List[Literal[0, 1, 2, 3, 4, 5, 6]] = MISSING, - n_weekday: Tuple[Literal[1, 2, 3, 4], int] = MISSING, - month: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] = MISSING, + start: datetime, + months: List[int] = MISSING, month_days: List[int] = MISSING, + weekdays: List[int] = MISSING, year_days: List[int] = MISSING, - end: Optional[datetime] = MISSING, + n_weekdays: List[NWeekdays] = MISSING, ) -> None: - if not start.tzinfo: raise ValueError( - '\'start\' must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' - ) - - if end not in (MISSING, None): - if not end.tzinfo: - raise ValueError( - '\'end\' must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ( + "'start' must be an aware datetime. Consider using discord.utils.utcnow()" + " or datetime.datetime.now().astimezone() for local time." ) + ) self.start: datetime = start - self.end: Optional[datetime] = end if end is not MISSING else None - - self.weekdays: Optional[List[int]] = weekdays if weekdays is not MISSING else None - self.n_weekday: Optional[Tuple[int, int]] = n_weekday if n_weekday is not MISSING else None - self.month: Optional[int] = month if month is not MISSING else None - self.month_days: Optional[List[int]] = month_days if month_days is not MISSING else None - self.year_days: Optional[List[int]] = year_days if year_days is not MISSING else None - self._interval: int = 1 - - def __eq__(self, other: object) -> bool: - if isinstance(other, self.__class__): - return ( - self.start == other.start - ) - return NotImplemented - - def __set_interval(self, value: int) -> None: - # Inner function to set the interval to the one that we - # recieved from the API - self._interval: int = value + self._months: List[int] = months + self._month_days: List[int] = month_days + self._weekdays: List[int] = weekdays + self._year_days: List[int] = year_days + self._n_weekdays: List[NWeekdays] = n_weekdays + + @property + def months(self) -> Optional[List[int]]: + """Optional[List[:class:`int`]]: The list of months this event will be repeated + on, or ``None``. + """ + return self._months if self._months is not MISSING else None + + @property + def month_days(self) -> Optional[List[int]]: + """Optional[List[:class:`int`]]: The list of month days this event will be repeated + on, or ``None``. + """ + return self._month_days if self._month_days is not MISSING else None @property - def frequency(self) -> int: - """:class:`int`: Returns the frequency of this recurrent scheduled event""" - - # This is now an internal parameter because if it is user-provided this could cause - # HTTPExceptions when creating or editing events. - - if self.weekdays is not None: - return 2 if len(self.weekdays) == 1 else 3 - elif self.n_weekday is not None: - return 1 - elif self.month is not None and self.month_days is not None: - return 0 - return 0 # In case None of the cases matches (i.e.: year_days) then we return 0 - + def weekdays(self) -> Optional[List[int]]: + """Optional[List[:class:`int`]]: The list of weekdays this event will be repeated + on, or ``None``. + """ + return self._weekdays if self._weekdays is not MISSING else None + @property - def interval(self) -> int: - """:class:`int`: Returns the interval of this recurrent scheduled event""" - return self._interval - - def to_dict(self) -> GuildScheduledEventRecurrencePayload: - return { - "start": self.start.isoformat(), - "end": self.end.isoformat() if self.end else None, - "by_weekday": self.weekdays or [], - "by_month": [self.month,] if self.month else [], - "by_month_day": self.month_days or [], - "by_n_weekday": [self.n_weekday,] if self.n_weekday else [], - "by_year_day": self.year_days or [], - "count": None, # There isn't counts, yet - "frequency": self.frequency, - "interval": self.interval, - } # type: ignore - - @classmethod - def from_dict(cls, data: GuildScheduledEventRecurrencePayload) -> ScheduledEventRecurrence: - self: cls = cls( - start=datetime.fromisoformat(data.get('start')), - weekdays=data.get('by_weekday', MISSING), - n_weekdays=((d['n'], d['day']) for d in data.get('by_n_weekday')) if data.get('by_n_weekday', MISSING) is not MISSING else MISSING, - month=data.get('by_month')[0] if len(data.get('by_month', [])) > 0 and data.get('by_month', MISSING) is not MISSING else MISSING, - month_days=data.get('by_month_day', MISSING), - year_days=data.get('by_year_day', MISSING), - end=data.get('end', MISSING) - ) # type: ignore - - self.__set_interval(int(data.get('interval', 1))) - - return self + def year_days(self) -> Optional[List[int]]: + """Optional[List[:class:`int`]]: The list of year days this event will be repeated + on, or ``None``. + """ + return self._year_days if self._year_days is not MISSING else None + + @property + def n_weekdays(self) -> Optional[List[NWeekdays]]: + """Optional[List[Tuple[:class:`int`, :class:`int`]]]: The ``(day, month)`` pairs + this event will be repeated on, or ``None``. + """ + return self._n_weekdays if self._n_weekdays is not MISSING else None + + def edit(self): + ... # TODO: finish this thingy class ScheduledEvent(Hashable): @@ -621,7 +543,7 @@ async def edit( image: bytes = MISSING, location: str = MISSING, reason: Optional[str] = None, - recurrence: Optional[ScheduledEventRecurrence] = MISSING, + recurrence_rule: Optional[ScheduledEventRecurrence] = MISSING, ) -> ScheduledEvent: r"""|coro| @@ -783,9 +705,9 @@ async def edit( else: payload['scheduled_end_time'] = end_time - if recurrence is not MISSING: - if recurrence is not None: - payload['recurrence_rule'] = recurrence.to_dict() + if recurrence_rule is not MISSING: + if recurrence_rule is not None: + payload['recurrence_rule'] = recurrence_rule.to_dict() else: payload['recurrence_rule'] = None From cd5cbc615a8c034e31d64113013a83bbe03cff09 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 16:17:03 +0200 Subject: [PATCH 04/46] ........ --- discord/scheduled_event.py | 25 ++++++++++++++++++------- discord/types/scheduled_event.py | 28 +++++++++++----------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 9a10defd2e7d..39651d4f3b59 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -193,6 +193,13 @@ def n_weekdays(self) -> Optional[List[NWeekdays]]: def edit(self): ... # TODO: finish this thingy + @classmethod + def from_dict(cls, data: GuildScheduledEventRecurrencePayload, /) -> ScheduledEventRecurrenceRule: + ... # TODO: finish this ALSO + + def to_dict(self) -> GuildScheduledEventRecurrencePayload: + ... # TODO: guessed it, finish this also + class ScheduledEvent(Hashable): """Represents a scheduled event in a guild. @@ -289,7 +296,11 @@ def _update(self, data: GuildScheduledEventPayload) -> None: self._cover_image: Optional[str] = data.get('image', None) self.user_count: int = data.get('user_count', 0) self.creator_id: Optional[int] = _get_as_snowflake(data, 'creator_id') - self.recurrence: Optional[ScheduledEventRecurrence] = ScheduledEventRecurrence.from_dict(data.get('recurrence_rule')) if data.get('recurrence_rule', None) is not None else None + + try: + self.recurrence: Optional[ScheduledEventRecurrenceRule] = ScheduledEventRecurrenceRule.from_dict(data['recurrence_rule']) + except KeyError: + self.recurrence = None creator = data.get('creator') self.creator: Optional[User] = self._state.store_user(creator) if creator else None @@ -455,7 +466,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -473,7 +484,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -491,7 +502,7 @@ async def edit( image: bytes = ..., location: str, reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -508,7 +519,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -525,7 +536,7 @@ async def edit( image: bytes = ..., location: str, reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -543,7 +554,7 @@ async def edit( image: bytes = MISSING, location: str = MISSING, reason: Optional[str] = None, - recurrence_rule: Optional[ScheduledEventRecurrence] = MISSING, + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = MISSING, ) -> ScheduledEvent: r"""|coro| diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index 4d76c2ef6f27..a8efc6825823 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -22,7 +22,7 @@ DEALINGS IN THE SOFTWARE. """ -from typing import List, Literal, Optional, TypedDict, Union, Dict +from typing import List, Literal, Optional, TypedDict, Union from typing_extensions import NotRequired from .snowflake import Snowflake @@ -36,24 +36,18 @@ class _NWeekday(TypedDict): n: int day: Literal[0, 1, 2, 3, 4, 5, 6] -class GuildScheduledEventRecurrence(TypedDict): + +class ScheduledEventRecurrenceRule(TypedDict): start: str - end: Optional[str] - frequency: int interval: int - by_weekday: Optional[List[Literal[0, 1, 2, 3, 4, 5, 6]]] # NOTE: 0 = monday; 6 = sunday - by_n_weekday: Optional[List[_NWeekday]] - by_month: Optional[List[Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]]] - by_month_day: Optional[List[int]] # NOTE: day range between 1 and 31 + frequency: int + count: None # no values for this, yet by_year_day: Optional[List[int]] - count: Optional[int] # maybe? -# NOTE: for this ^ enum, it is recommended to use "calendar" module constants: MONDAY; TUESDAY; WEDNESDAY; etc -# as they follow these patterns and is a built-in module. + by_weekday: Optional[List[int]] + by_month_day: Optional[List[int]] + by_month: Optional[List[int]] + by_n_weekday: NotRequired[List[_NWeekday]] -class GuildScheduledEventExceptionCounts(TypedDict): - guild_scheduled_event_count: int - guild_scheduled_event_exception_counts: Dict[Snowflake, int] -# NOTE: This class doesn't represent any of the user counts or the 'count' param in recurrence class _BaseGuildScheduledEvent(TypedDict): id: Snowflake @@ -64,8 +58,8 @@ class _BaseGuildScheduledEvent(TypedDict): privacy_level: PrivacyLevel status: EventStatus auto_start: bool - guild_scheduled_events_exceptions: List # Didn't found items in the list yet - recurrence_rule: Optional[GuildScheduledEventRecurrence] + guild_scheduled_events_exceptions: List[int] + recurrence_rule: Optional[ScheduledEventRecurrenceRule] sku_ids: List[Snowflake] creator_id: NotRequired[Optional[Snowflake]] description: NotRequired[Optional[str]] From f1ea23aa70778421b27fccfe95d07170c7968eb7 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 16:17:57 +0200 Subject: [PATCH 05/46] space --- discord/types/scheduled_event.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index a8efc6825823..53b6a428556d 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -29,6 +29,7 @@ from .user import User from .member import Member from .channel import PrivacyLevel as PrivacyLevel + EventStatus = Literal[1, 2, 3, 4] EntityType = Literal[1, 2, 3] From dd780af237751179788bd37540d8ba5f57c4f1ec Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 16:18:42 +0200 Subject: [PATCH 06/46] fix imports --- discord/guild.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index ee0ed67be743..e6742c3bfa45 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -84,7 +84,7 @@ from .asset import Asset from .flags import SystemChannelFlags from .integrations import Integration, PartialIntegration, _integration_factory -from .scheduled_event import ScheduledEvent, ScheduledEventRecurrence +from .scheduled_event import ScheduledEvent, ScheduledEventRecurrenceRule from .stage_instance import StageInstance from .threads import Thread, ThreadMember from .sticker import GuildSticker @@ -3038,7 +3038,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3055,7 +3055,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3071,7 +3071,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3087,7 +3087,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrence] = ..., + recurrence: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3104,7 +3104,7 @@ async def create_scheduled_event( description: str = MISSING, image: bytes = MISSING, reason: Optional[str] = None, - recurrence: Optional[ScheduledEventRecurrence] = MISSING, + recurrence: Optional[ScheduledEventRecurrenceRule] = MISSING, ) -> ScheduledEvent: r"""|coro| From ad8796272eb255834076c89670cf1c690342d1ca Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 21:54:05 +0200 Subject: [PATCH 07/46] overloads fix --- discord/guild.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index e6742c3bfa45..cb4fb8de84e7 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3038,7 +3038,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3055,7 +3055,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3071,7 +3071,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3087,7 +3087,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3104,7 +3104,7 @@ async def create_scheduled_event( description: str = MISSING, image: bytes = MISSING, reason: Optional[str] = None, - recurrence: Optional[ScheduledEventRecurrenceRule] = MISSING, + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = MISSING, ) -> ScheduledEvent: r"""|coro| @@ -3151,7 +3151,7 @@ async def create_scheduled_event( Required if the ``entity_type`` is :attr:`EntityType.external`. reason: Optional[:class:`str`] The reason for creating this scheduled event. Shows up on the audit log. - recurrence: Optional[:class:`ScheduledEventRecurrence`] + recurrence_rule: Optional[:class:`ScheduledEventRecurrenceRule`] The recurrence rule this event will follow. If this is `None` then this is a one-time event. @@ -3248,8 +3248,8 @@ async def create_scheduled_event( ) payload['scheduled_end_time'] = end_time.isoformat() - if recurrence not in (MISSING, None): - payload['recurrence_rule'] = recurrence.to_dict() + if recurrence_rule not in (MISSING, None): + payload['recurrence_rule'] = recurrence_rule.to_dict() if metadata: payload['entity_metadata'] = metadata From aa6e4d982b78411bc9387ccca62942a4402043d7 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 21:54:56 +0200 Subject: [PATCH 08/46] overloads fix --- discord/scheduled_event.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 39651d4f3b59..7382db118cd7 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -466,7 +466,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -484,7 +484,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -502,7 +502,7 @@ async def edit( image: bytes = ..., location: str, reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -519,7 +519,7 @@ async def edit( status: EventStatus = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -536,7 +536,7 @@ async def edit( image: bytes = ..., location: str, reason: Optional[str] = ..., - recurrence: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... From f54ec220f54c661e5c75cb16c00e9e36006f3141 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 21:55:34 +0200 Subject: [PATCH 09/46] not in (MISSING, None) -> not in MISSING --- discord/guild.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index cb4fb8de84e7..3edc88f33c48 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3248,8 +3248,11 @@ async def create_scheduled_event( ) payload['scheduled_end_time'] = end_time.isoformat() - if recurrence_rule not in (MISSING, None): - payload['recurrence_rule'] = recurrence_rule.to_dict() + if recurrence_rule is not MISSING: + if recurrence_rule is not None: + payload['recurrence_rule'] = recurrence_rule.to_dict() + else: + payload['recurrence_rule'] = None if metadata: payload['entity_metadata'] = metadata From 32e673986711997e2679eca02cefa57a21de276e Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 7 Jun 2024 21:57:34 +0200 Subject: [PATCH 10/46] first docs commit --- docs/api.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 41cf6549d169..a8b7ee66edc6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5407,6 +5407,15 @@ PollMedia :members: +ScheduledEventRecurrenceRule +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. attributetable:: ScheduledEventRecurrenceRule + +.. autoclass:: ScheduledEventRecurrenceRule + :members: + + Exceptions ------------ From 9afda1cd7e5932f95fcede9d849a718b578878f6 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 11:51:24 +0200 Subject: [PATCH 11/46] First commit on the new recurrence rule objects --- discord/scheduled_event.py | 274 +++++++++++++++++++------------ discord/types/scheduled_event.py | 2 +- 2 files changed, 173 insertions(+), 103 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 7382db118cd7..93083341f4d1 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -24,7 +24,8 @@ from __future__ import annotations -from datetime import datetime +import calendar +from datetime import datetime, date from typing import ( TYPE_CHECKING, AsyncIterator, @@ -36,19 +37,25 @@ List, NamedTuple, ) +from dateutil import rrule from .asset import Asset -from .enums import EventStatus, EntityType, PrivacyLevel, try_enum +from .enums import ( + EventStatus, + EntityType, + PrivacyLevel, + try_enum +) from .mixins import Hashable from .object import Object, OLDEST_OBJECT from .utils import parse_time, _get_as_snowflake, _bytes_to_base64_data, MISSING if TYPE_CHECKING: + from typing_extensions import Self + from .types.scheduled_event import ( GuildScheduledEvent as BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCount as GuildScheduledEventWithUserCountPayload, - GuildScheduledEventRecurrence as GuildScheduledEventRecurrencePayload, - GuildScheduledEventExceptionCounts as GuildScheduledEventExceptionCountsPayload, EntityMetadata, ) @@ -66,139 +73,202 @@ ) # fmt: on -class NWeekdays(NamedTuple): - n: int - day: int + +class _NWeekday(NamedTuple): + week: Literal[1, 2, 3, 4, 5] # "n" for the API + day: rrule.weekday class ScheduledEventRecurrenceRule: - """Represents a scheduled event recurrence rule. + """The recurrence rule for a scheduled event. - .. versionadded:: 2.4 + This follows :class:`dateutil.rrule.rrule` structure. Parameters ---------- start: :class:`datetime.datetime` - An aware datetime object representing the recurrence start time. - months: List[:class:`int`] - The months this event will be repeated on. - month_days: List[:class:`int`] - The month days this event will be repeated on. - weekdays: List[:class:`int`] - The weekdays this event will be reapeated on. - year_days: List[:class:`int`] - The year days this event will be repeated on. - n_weekdays: List[Tuple[:class:`int`, :class:`int`]] - A ``(day, month)`` tuple that represents the N day of a month that this event - will be repeated on. + The datetime when the recurrence interval starts. + frequency: :class:`int` + How often the event occurs. + + This can be one of :attr:`dateutil.rrule.YEARLY`, :attr:`dateutil.rrule.MONTHLY`, + :attr:`dateutil.rrule.WEEKLY`, or :attr:`dateutil.rrule.DAILY`. """ - @overload - def __init__( - self, - *, - start: datetime, - months: List[int], - month_days: List[int], - ) -> None: - ... + # As noted in the docs, recurrence rule is implemented by dateutil.rrule so we use + # that to have a better control on this. - @overload def __init__( self, - *, + /, start: datetime, - weekdays: List[int], - ) -> None: - ... - - @overload - def __init__( - self, + frequency: Literal[0, 1, 2, 3,], + interval: int, *, - start: datetime, - year_days: List[int], + weekdays: Optional[List[rrule.weekday]] = MISSING, + n_weekdays: Optional[List[_NWeekday]] = MISSING, + month_days: Optional[List[date]] = MISSING, ) -> None: - ... + self._rule: rrule.rrule = rrule.rrule( + frequency, + start, + interval, + ) + self._weekdays = weekdays + self._n_weekdays = n_weekdays + self._month_days = month_days + + kwargs = {} + + if weekdays not in (MISSING, None): + kwargs.update(byweekday=[wkday.weekday for wkday in weekdays]) + if n_weekdays not in (MISSING, None): + n_wkno = [] + n_wkdays = [] + + for n_wkday in n_weekdays: + n_wkno.append(n_wkday[0]) + n_wkdays.append(n_wkday[1]) + + kwargs.update( + byweekno=n_wkno, + byweekday=n_wkdays, + ) - @overload - def __init__( - self, - *, - start: datetime, - n_weekdays: List[NWeekdays], - ) -> None: - ... + del n_wkno, n_wkdays - def __init__( - self, - *, - start: datetime, - months: List[int] = MISSING, - month_days: List[int] = MISSING, - weekdays: List[int] = MISSING, - year_days: List[int] = MISSING, - n_weekdays: List[NWeekdays] = MISSING, - ) -> None: - if not start.tzinfo: - raise ValueError( - ( - "'start' must be an aware datetime. Consider using discord.utils.utcnow()" - " or datetime.datetime.now().astimezone() for local time." - ) + if month_days not in (MISSING, None): + month_days_months = [] + month_days_days = [] + + for month_day in month_days: + month_days_months.append(month_day.month) + month_days_days.append(month_day.day) + + kwargs.update( + bymonth=month_days_months, + bymonthday=month_days_days, ) - self.start: datetime = start - self._months: List[int] = months - self._month_days: List[int] = month_days - self._weekdays: List[int] = weekdays - self._year_days: List[int] = year_days - self._n_weekdays: List[NWeekdays] = n_weekdays + del month_days_months, month_days_days - @property - def months(self) -> Optional[List[int]]: - """Optional[List[:class:`int`]]: The list of months this event will be repeated - on, or ``None``. - """ - return self._months if self._months is not MISSING else None + if kwargs: + self._rule = self._rule.replace(**kwargs) @property - def month_days(self) -> Optional[List[int]]: - """Optional[List[:class:`int`]]: The list of month days this event will be repeated - on, or ``None``. + def weekdays(self) -> Optional[List[rrule.weekday]]: + """Optional[List[:class:`dateutil.rrule.weekday`]]: Returns a read-only list of the weekdays + this event recurs on, or ``None``. """ - return self._month_days if self._month_days is not MISSING else None + if self._weekdays in (MISSING, None): + return None + return self._weekdays.copy() + + @weekdays.setter + def weekdays(self, new: Optional[List[rrule.weekday]]) -> None: + self.replace(weekdays=new) @property - def weekdays(self) -> Optional[List[int]]: - """Optional[List[:class:`int`]]: The list of weekdays this event will be repeated - on, or ``None``. + def n_weekdays(self) -> Optional[List[_NWeekday]]: + """Optional[List[Tuple[:class:`int`, :class:`dateutil.rrule.weekday`]]]: Returns a read-only + list of the N weekdays this event recurs on, or ``None``. """ - return self._weekdays if self._weekdays is not MISSING else None + if self._n_weekdays in (MISSING, None): + return None + return self._n_weekdays.copy() + + @n_weekdays.setter + def n_weekdays(self, new: Optional[List[_NWeekday]]) -> None: + self.replace(n_weekdays=new) @property - def year_days(self) -> Optional[List[int]]: - """Optional[List[:class:`int`]]: The list of year days this event will be repeated - on, or ``None``. + def month_days(self) -> Optional[List[date]]: + """Optional[List[:class:`datetime.date`]]: Returns a read-only list of the month days this + event recurs on, or ``None``. """ - return self._year_days if self._year_days is not MISSING else None + if self._month_days in (MISSING, None): + return None + return self._month_days.copy() - @property - def n_weekdays(self) -> Optional[List[NWeekdays]]: - """Optional[List[Tuple[:class:`int`, :class:`int`]]]: The ``(day, month)`` pairs - this event will be repeated on, or ``None``. + @month_days.setter + def month_days(self, new: Optional[List[date]]) -> None: + self.replace(month_days=new) + + def replace( + self, + *, + weekdays: Optional[List[rrule.weekday]] = MISSING, + n_weekdays: Optional[List[_NWeekday]] = MISSING, + month_days: Optional[List[date]] = MISSING, + ) -> Self: + """Replaces and returns the recurrence rule with the same values except for the + ones that are changed. + + This is similar to :meth:`dateutil.rrule.rrule.replace`. + + Parameters + ---------- + weekdays: Optional[List[:class:`dateutil.rrule.weekday`]] + The new weekdays for the event to recur on. + n_weekdays: Optional[List[Tuple[:class:`int`, :class:`dateutil.rrule.weekday`]]] + 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. + + .. note:: + + :attr:`datetime.date.year` attribute is ignored when updating the recurrence + rule. + + Returns + ------- + :class:`ScheduledEventRecurrenceRule` + The recurrence rule with the replaced values. """ - return self._n_weekdays if self._n_weekdays is not MISSING else None - def edit(self): - ... # TODO: finish this thingy + kwargs = {} + + if weekdays is not MISSING: + if weekdays is None: + kwargs.update(byweekday=None) + else: + kwargs.update(byweekday=[wkday.weekday for wkday in weekdays]) + + if n_weekdays is not MISSING: + if n_weekdays is None: + kwargs.update(byweekno=None, byweekday=None) + else: + n_wkno = [] + n_wkdays = [] - @classmethod - def from_dict(cls, data: GuildScheduledEventRecurrencePayload, /) -> ScheduledEventRecurrenceRule: - ... # TODO: finish this ALSO + for n_wkday in n_weekdays: + n_wkno.append(n_wkday[0]) + n_wkdays.append(n_wkdays[1]) + kwargs.update(byweekno=n_wkno, byweekday=n_wkdays) + + del n_wkno, n_wkdays + + if month_days is not MISSING: + if month_days is None: + kwargs.update(bymonth=None, bymonthday=None) + else: + month_days_months = [] + month_days_days = [] + + for month_day in month_days: + month_days_months.append(month_day.month) + month_days_days.append(month_day.day) + kwargs.update(bymonth=month_days_months, bymonthday=month_days_days) + + del month_days_months, month_days_days + + if not kwargs: + raise ValueError( + 'You must provide at least one value to replace on the recurrence rule' + ) - def to_dict(self) -> GuildScheduledEventRecurrencePayload: - ... # TODO: guessed it, finish this also + self._rule = self._rule.replace(**kwargs) + return self class ScheduledEvent(Hashable): diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index 53b6a428556d..d24bfc571b84 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -38,7 +38,7 @@ class _NWeekday(TypedDict): day: Literal[0, 1, 2, 3, 4, 5, 6] -class ScheduledEventRecurrenceRule(TypedDict): +class GuildScheduledEventRecurrenceRule(TypedDict): start: str interval: int frequency: int From 96e9be5af2b27f964cf64118621842eeb0e90daa Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 12:10:01 +0200 Subject: [PATCH 12/46] fixed as I didnt knew dateutil isnt built-in lmao --- discord/scheduled_event.py | 132 +++++++++---------------------------- 1 file changed, 32 insertions(+), 100 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 93083341f4d1..df9270c6e0f5 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -30,14 +30,13 @@ TYPE_CHECKING, AsyncIterator, Dict, + NamedTuple, Optional, Union, overload, Literal, List, - NamedTuple, ) -from dateutil import rrule from .asset import Asset from .enums import ( @@ -75,8 +74,8 @@ class _NWeekday(NamedTuple): - week: Literal[1, 2, 3, 4, 5] # "n" for the API - day: rrule.weekday + week: Literal[1, 2, 3, 4, 5] + day: Literal[0, 1, 2, 3, 4, 5, 6] class ScheduledEventRecurrenceRule: @@ -84,6 +83,8 @@ class ScheduledEventRecurrenceRule: This follows :class:`dateutil.rrule.rrule` structure. + .. versionadded:: 2.5 + Parameters ---------- start: :class:`datetime.datetime` @@ -93,11 +94,19 @@ class ScheduledEventRecurrenceRule: This can be one of :attr:`dateutil.rrule.YEARLY`, :attr:`dateutil.rrule.MONTHLY`, :attr:`dateutil.rrule.WEEKLY`, or :attr:`dateutil.rrule.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:`datetime.date`]] + The weekdays the event will recur on. + n_weekdays: Optional[List[Tuple[:class:`int`, :class:`int`]]] + 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. """ - # As noted in the docs, recurrence rule is implemented by dateutil.rrule so we use - # that to have a better control on this. - def __init__( self, /, @@ -105,58 +114,19 @@ def __init__( frequency: Literal[0, 1, 2, 3,], interval: int, *, - weekdays: Optional[List[rrule.weekday]] = MISSING, + weekdays: Optional[List[date]] = MISSING, n_weekdays: Optional[List[_NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> None: - self._rule: rrule.rrule = rrule.rrule( - frequency, - start, - interval, - ) - self._weekdays = weekdays - self._n_weekdays = n_weekdays - self._month_days = month_days - - kwargs = {} - - if weekdays not in (MISSING, None): - kwargs.update(byweekday=[wkday.weekday for wkday in weekdays]) - if n_weekdays not in (MISSING, None): - n_wkno = [] - n_wkdays = [] - - for n_wkday in n_weekdays: - n_wkno.append(n_wkday[0]) - n_wkdays.append(n_wkday[1]) - - kwargs.update( - byweekno=n_wkno, - byweekday=n_wkdays, - ) - - del n_wkno, n_wkdays - - if month_days not in (MISSING, None): - month_days_months = [] - month_days_days = [] - - for month_day in month_days: - month_days_months.append(month_day.month) - month_days_days.append(month_day.day) - - kwargs.update( - bymonth=month_days_months, - bymonthday=month_days_days, - ) - - del month_days_months, month_days_days - - if kwargs: - self._rule = self._rule.replace(**kwargs) + self.start: datetime = start + self.frequency: Literal[0, 1, 2, 3,] = frequency + self.interval: int = interval + self._weekdays: Optional[List[date]] = weekdays + self._n_weekdays: Optional[List[_NWeekday]] = n_weekdays + self._month_days: Optional[List[date]] = month_days @property - def weekdays(self) -> Optional[List[rrule.weekday]]: + def weekdays(self) -> Optional[List[date]]: """Optional[List[:class:`dateutil.rrule.weekday`]]: Returns a read-only list of the weekdays this event recurs on, or ``None``. """ @@ -165,8 +135,8 @@ def weekdays(self) -> Optional[List[rrule.weekday]]: return self._weekdays.copy() @weekdays.setter - def weekdays(self, new: Optional[List[rrule.weekday]]) -> None: - self.replace(weekdays=new) + def weekdays(self, new: Optional[List[date]]) -> None: + self._weekdays = new @property def n_weekdays(self) -> Optional[List[_NWeekday]]: @@ -179,7 +149,7 @@ def n_weekdays(self) -> Optional[List[_NWeekday]]: @n_weekdays.setter def n_weekdays(self, new: Optional[List[_NWeekday]]) -> None: - self.replace(n_weekdays=new) + self._n_weekdays = new @property def month_days(self) -> Optional[List[date]]: @@ -192,12 +162,12 @@ def month_days(self) -> Optional[List[date]]: @month_days.setter def month_days(self, new: Optional[List[date]]) -> None: - self.replace(month_days=new) + self._month_days = new def replace( self, *, - weekdays: Optional[List[rrule.weekday]] = MISSING, + weekdays: Optional[List[date]] = MISSING, n_weekdays: Optional[List[_NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> Self: @@ -215,59 +185,21 @@ def replace( month_days: Optional[List[:class:`datetime.date`]] The new set of month and month days for the event to recur on. - .. note:: - - :attr:`datetime.date.year` attribute is ignored when updating the recurrence - rule. - Returns ------- :class:`ScheduledEventRecurrenceRule` The recurrence rule with the replaced values. """ - kwargs = {} - if weekdays is not MISSING: - if weekdays is None: - kwargs.update(byweekday=None) - else: - kwargs.update(byweekday=[wkday.weekday for wkday in weekdays]) + self._weekdays = weekdays if n_weekdays is not MISSING: - if n_weekdays is None: - kwargs.update(byweekno=None, byweekday=None) - else: - n_wkno = [] - n_wkdays = [] - - for n_wkday in n_weekdays: - n_wkno.append(n_wkday[0]) - n_wkdays.append(n_wkdays[1]) - kwargs.update(byweekno=n_wkno, byweekday=n_wkdays) - - del n_wkno, n_wkdays + self._n_weekdays = n_weekdays if month_days is not MISSING: - if month_days is None: - kwargs.update(bymonth=None, bymonthday=None) - else: - month_days_months = [] - month_days_days = [] - - for month_day in month_days: - month_days_months.append(month_day.month) - month_days_days.append(month_day.day) - kwargs.update(bymonth=month_days_months, bymonthday=month_days_days) - - del month_days_months, month_days_days - - if not kwargs: - raise ValueError( - 'You must provide at least one value to replace on the recurrence rule' - ) + self._month_days = month_days - self._rule = self._rule.replace(**kwargs) return self From 29adc265503f4309a0d8de55abdd717508df94e6 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 12:12:26 +0200 Subject: [PATCH 13/46] Add ScheduledEventRecurrenceRule to __all__ --- discord/scheduled_event.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index df9270c6e0f5..a74f422ec1af 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -69,6 +69,7 @@ # fmt: off __all__ = ( "ScheduledEvent", + "ScheduledEventRecurrenceRule", ) # fmt: on From c6eea299be201e230e919c60f5b1e49460b1598b Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 12:14:29 +0200 Subject: [PATCH 14/46] ScheduledEvent.recurrence -> ScheduledEvent.recurrence_rule --- discord/scheduled_event.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index a74f422ec1af..b0c43141865e 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -253,10 +253,10 @@ class ScheduledEvent(Hashable): .. versionadded:: 2.2 location: Optional[:class:`str`] The location of the scheduled event. - recurrence: Optional[:class:`ScheduledEventRecurrence`] - The recurrence rule this event follows, if any. + recurrence_rule: Optional[:class:`ScheduledEventRecurrence`] + The recurrence rule for this event, or ``None``. - .. versionadded:: 2.4 + .. versionadded:: 2.5 """ __slots__ = ( @@ -278,7 +278,7 @@ class ScheduledEvent(Hashable): 'channel_id', 'creator_id', 'location', - 'recurrence', + 'recurrence_rule', ) def __init__(self, *, state: ConnectionState, data: GuildScheduledEventPayload) -> None: @@ -300,10 +300,12 @@ def _update(self, data: GuildScheduledEventPayload) -> None: 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] + try: - self.recurrence: Optional[ScheduledEventRecurrenceRule] = ScheduledEventRecurrenceRule.from_dict(data['recurrence_rule']) + self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(data['recurrence_rule']) except KeyError: - self.recurrence = None + self.recurrence_rule = None creator = data.get('creator') self.creator: Optional[User] = self._state.store_user(creator) if creator else None From 427f1e8e85c2a8b8fb4b53680bf1b85a58dd7dba Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 12:19:55 +0200 Subject: [PATCH 15/46] Docstring fixes --- discord/scheduled_event.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index b0c43141865e..22913a0c4b74 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -80,9 +80,7 @@ class _NWeekday(NamedTuple): class ScheduledEventRecurrenceRule: - """The recurrence rule for a scheduled event. - - This follows :class:`dateutil.rrule.rrule` structure. + r"""The recurrence rule for a scheduled event. .. versionadded:: 2.5 @@ -93,8 +91,19 @@ class ScheduledEventRecurrenceRule: frequency: :class:`int` How often the event occurs. - This can be one of :attr:`dateutil.rrule.YEARLY`, :attr:`dateutil.rrule.MONTHLY`, - :attr:`dateutil.rrule.WEEKLY`, or :attr:`dateutil.rrule.DAILY`. + 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``. @@ -175,8 +184,6 @@ def replace( """Replaces and returns the recurrence rule with the same values except for the ones that are changed. - This is similar to :meth:`dateutil.rrule.rrule.replace`. - Parameters ---------- weekdays: Optional[List[:class:`dateutil.rrule.weekday`]] From a2c431c21c42707323f1bb5bb1ba02ce1747961e Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 12:22:24 +0200 Subject: [PATCH 16/46] More docstring fixes --- discord/scheduled_event.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 22913a0c4b74..499da61dea85 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -137,7 +137,7 @@ def __init__( @property def weekdays(self) -> Optional[List[date]]: - """Optional[List[:class:`dateutil.rrule.weekday`]]: Returns a read-only list of the weekdays + """Optional[List[:class:`datetime.date`]]: Returns a read-only list of the weekdays this event recurs on, or ``None``. """ if self._weekdays in (MISSING, None): @@ -150,7 +150,7 @@ def weekdays(self, new: Optional[List[date]]) -> None: @property def n_weekdays(self) -> Optional[List[_NWeekday]]: - """Optional[List[Tuple[:class:`int`, :class:`dateutil.rrule.weekday`]]]: Returns a read-only + """Optional[List[Tuple[:class:`int`, :class:`int`]]]: Returns a read-only list of the N weekdays this event recurs on, or ``None``. """ if self._n_weekdays in (MISSING, None): @@ -186,9 +186,9 @@ def replace( Parameters ---------- - weekdays: Optional[List[:class:`dateutil.rrule.weekday`]] + weekdays: Optional[List[:class:`datetime.date`]] The new weekdays for the event to recur on. - n_weekdays: Optional[List[Tuple[:class:`int`, :class:`dateutil.rrule.weekday`]]] + n_weekdays: Optional[List[Tuple[:class:`int`, :class:`int`]]] 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. From 478a193ca99e6e84b09a26b11a20cf7982628823 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 13:31:27 +0200 Subject: [PATCH 17/46] More changes --- discord/guild.py | 2 +- discord/scheduled_event.py | 117 ++++++++++++++++++++++++++++--- discord/types/scheduled_event.py | 16 +++-- 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 89b64c465afc..0ca06ee1edef 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3250,7 +3250,7 @@ async def create_scheduled_event( if recurrence_rule is not MISSING: if recurrence_rule is not None: - payload['recurrence_rule'] = recurrence_rule.to_dict() + payload['recurrence_rule'] = recurrence_rule._to_dict() else: payload['recurrence_rule'] = None diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 499da61dea85..87f7ca4d721b 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -24,7 +24,6 @@ from __future__ import annotations -import calendar from datetime import datetime, date from typing import ( TYPE_CHECKING, @@ -55,6 +54,8 @@ from .types.scheduled_event import ( GuildScheduledEvent as BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCount as GuildScheduledEventWithUserCountPayload, + ScheduledEventRecurrenceRule as ScheduledEventRecurrenceRulePayload, + _NWeekday as NWeekdayPayload, EntityMetadata, ) @@ -109,14 +110,33 @@ class ScheduledEventRecurrenceRule: 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:`datetime.date`]] + weekdays: Optional[List[:class:`int`]] 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`]]] 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. """ + __slots__ = ( + # Attributes user can set: + 'start', + 'frequency', + 'interval', + '_weekdays', + '_n_weekdays', + '_month_days', + + # Attributes that are returned by API only: + '_count', + '_end', + '_year_days', + ) + def __init__( self, /, @@ -124,28 +144,31 @@ def __init__( frequency: Literal[0, 1, 2, 3,], interval: int, *, - weekdays: Optional[List[date]] = MISSING, + weekdays: Optional[List[int]] = 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.interval: int = interval - self._weekdays: Optional[List[date]] = weekdays + self._count: Optional[int] = None + self._end: Optional[datetime] = None + self._year_days: Optional[List[int]] = None + self._weekdays: Optional[List[int]] = weekdays self._n_weekdays: Optional[List[_NWeekday]] = n_weekdays self._month_days: Optional[List[date]] = month_days @property - def weekdays(self) -> Optional[List[date]]: - """Optional[List[:class:`datetime.date`]]: Returns a read-only list of the weekdays - this event recurs on, or ``None``. + def weekdays(self) -> Optional[List[int]]: + """Optional[List[:class:`int`]]: Returns a read-only list of the weekdays this event + recurs on, or ``None``. """ if self._weekdays in (MISSING, None): return None return self._weekdays.copy() @weekdays.setter - def weekdays(self, new: Optional[List[date]]) -> None: + def weekdays(self, new: Optional[List[int]]) -> None: self._weekdays = new @property @@ -174,6 +197,28 @@ def month_days(self) -> Optional[List[date]]: def month_days(self, new: Optional[List[date]]) -> None: self._month_days = new + @property + def end(self) -> Optional[datetime]: + """Optional[:class:`datetime.datetime`]: The ending time of the recurrence interval, + or ``None``. + """ + return self._end + + @property + def count(self) -> Optional[int]: + """Optional[:class:`int`]: The amount of times the event will recur before stopping, + or ``None`` if it recurs forever. + """ + + @property + def year_days(self) -> Optional[List[int]]: + """Optional[List[:class:`int`]]: Returns a read-only list of the year days this + event recurs on, or ``None``. + """ + if self._year_days is None: + return None + return self._year_days.copy() + def replace( self, *, @@ -210,6 +255,62 @@ def replace( return self + def _to_dict(self) -> ScheduledEventRecurrenceRulePayload: + + by_weekday: Optional[List[int]] = None + by_n_weekday: Optional[List[NWeekdayPayload]] = None + by_month: Optional[List[int]] = None + by_month_day: Optional[List[int]] = None + by_year_day: Optional[List[int]] = None + + if self._weekdays not in (MISSING, None): + by_weekday = self._weekdays + + if self._n_weekdays not in (MISSING, None): + by_n_weekday = [ + {'n': n, 'day': day} for n, day in self._n_weekdays + ] + + if self._month_days not in (MISSING, None): + by_month = [] + by_month_day = [] + + for dt in self._month_days: + by_month.append(dt.month) + by_month_day.append(dt.day) + + if self.year_days is not None: + by_year_day = self.year_days + + return { + 'start': self.start.isoformat(), + 'end': self._end.isoformat() if self._end is not None else None, + 'frequency': self.frequency, + 'interval': self.interval, + 'by_weekday': by_weekday, + 'by_n_weekday': by_n_weekday, + 'by_month': by_month, + 'by_month_day': by_month_day, + 'by_year_day': by_year_day, + 'count': self.count, + } + + @classmethod + def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: + self = cls( + start=parse_time(data['start']), + frequency=data['frequency'], + interval=data['interval'], + ) + 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) + # TODO: finish this impl + return self + class ScheduledEvent(Hashable): """Represents a scheduled event in a guild. diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index d24bfc571b84..3d7aeec83bc4 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -32,22 +32,24 @@ EventStatus = Literal[1, 2, 3, 4] EntityType = Literal[1, 2, 3] +ScheduledEventRecurrenceRuleFrequency = Literal[0, 1, 2, 3] class _NWeekday(TypedDict): n: int - day: Literal[0, 1, 2, 3, 4, 5, 6] + day: int -class GuildScheduledEventRecurrenceRule(TypedDict): +class ScheduledEventRecurrenceRule(TypedDict): start: str + end: Optional[str] + frequency: ScheduledEventRecurrenceRuleFrequency interval: int - frequency: int - count: None # no values for this, yet - by_year_day: Optional[List[int]] by_weekday: Optional[List[int]] - by_month_day: Optional[List[int]] + by_n_weekday: Optional[List[_NWeekday]] by_month: Optional[List[int]] - by_n_weekday: NotRequired[List[_NWeekday]] + by_month_day: Optional[List[int]] + by_year_day: Optional[List[int]] + count: Optional[int] class _BaseGuildScheduledEvent(TypedDict): From 73ff797de33e0fa49bb178f161efa886d186bfc9 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 13:36:08 +0200 Subject: [PATCH 18/46] Remove ScheduledEvent.fetch_counts --- discord/scheduled_event.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 87f7ca4d721b..5f067730f814 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -959,40 +959,6 @@ async def _after_strategy(retrieve: int, after: Optional[Snowflake], limit: Opti # There's no data left after this break - async def fetch_counts(self, *children: Snowflake) -> ScheduledEventExceptionCount: - """|coro| - - Retrieves all the counts for this Event children, if this event isn't - recurrent, then this will return `None`. - - This also contains the exceptions of this Scheduled event. - - .. versionadded:: 2.4 - - Parameters - ---------- - *children: :class:`Snowflake` - The snowflakes of the children to fetcht the counts of. - - Raises - ------ - HTTPException - Fetching the counts failed. - - Returns - ------- - Optional[:class:`ScheduledEventExceptionCount`] - The counts of this event, or `None` if this event isn't recurrent or - there isn't any exception. - """ - - if not self.recurrence: - return None - - data = await self._state.http.get_scheduled_event_counts(self.guild_id, self.id, tuple([child.id for child in children])) - - return ScheduledEventExceptionCount(data) - def _add_user(self, user: User) -> None: self._users[user.id] = user From 0f2f84d3c249765e5ef914612168899791a9d7a3 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 13:40:11 +0200 Subject: [PATCH 19/46] Remove get_scheduled_event_counts from HTTPClient and more type fixes --- discord/http.py | 21 --------------------- discord/scheduled_event.py | 4 ++-- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/discord/http.py b/discord/http.py index 6e1bc3d1c27d..abf6a9b239b3 100644 --- a/discord/http.py +++ b/discord/http.py @@ -2170,27 +2170,6 @@ def get_scheduled_event_users( ), params=params, ) - - def get_scheduled_event_counts( - self, - guild_id: Snowflake, - guild_scheduled_event_id: Snowflake, - scheduled_event_exception_ids: Tuple[Snowflake, ...] - ) -> Response[scheduled_event.GuildScheduledEventExceptionCounts]: - route: str = '/guilds/{guild_id}/scheduled-events/{guild_scheduled_event_id}/users/counts?' - - if len(scheduled_event_exception_ids) > 0: - for exception_id in scheduled_event_exception_ids: - route += f"guild_scheduled_event_exception_ids={exception_id}&" - - return self.request( - Route( - 'GET', - route, - guild_id=guild_id, - guild_scheduled_event_id=guild_scheduled_event_id - ) - ) # Application commands (global) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 5f067730f814..905f989fd61a 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -222,7 +222,7 @@ def year_days(self) -> Optional[List[int]]: def replace( self, *, - weekdays: Optional[List[date]] = MISSING, + weekdays: Optional[List[int]] = MISSING, n_weekdays: Optional[List[_NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> Self: @@ -231,7 +231,7 @@ def replace( Parameters ---------- - weekdays: Optional[List[:class:`datetime.date`]] + weekdays: Optional[List[:class:`int`]] The new weekdays for the event to recur on. n_weekdays: Optional[List[Tuple[:class:`int`, :class:`int`]]] The new set of specific days within a week for the event to recur on. From 12ec0ac4eef870449c5d219d9c1e096749ad2620 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 13:41:35 +0200 Subject: [PATCH 20/46] Fix ScheduledEvent.edit --- discord/scheduled_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 905f989fd61a..81fee1c20f36 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -831,7 +831,7 @@ async def edit( if recurrence_rule is not MISSING: if recurrence_rule is not None: - payload['recurrence_rule'] = recurrence_rule.to_dict() + payload['recurrence_rule'] = recurrence_rule._to_dict() else: payload['recurrence_rule'] = None From 1ec6ad79acffd3eff064df48e51f44b5e95277cb Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Tue, 6 Aug 2024 13:44:08 +0200 Subject: [PATCH 21/46] type fixes --- discord/scheduled_event.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 81fee1c20f36..9717fa0ce6fd 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -408,12 +408,11 @@ def _update(self, data: GuildScheduledEventPayload) -> None: 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] + self.recurrence_rule: Optional[ScheduledEventRecurrenceRule] = None + recurrence_rule_data = data.get('recurrence_rule') - try: - self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(data['recurrence_rule']) - except KeyError: - self.recurrence_rule = None + if recurrence_rule_data is not None: + self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(recurrence_rule_data) creator = data.get('creator') self.creator: Optional[User] = self._state.store_user(creator) if creator else None From 8f9c19f3f249866a8ca524b949feb80fb7ff2d54 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 7 Aug 2024 16:22:14 +0200 Subject: [PATCH 22/46] more changes --- discord/enums.py | 19 +++++ discord/scheduled_event.py | 138 ++++++++++++++++++++++++++----------- 2 files changed, 115 insertions(+), 42 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index eaf8aef5e058..6b47d17b8d29 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -74,6 +74,8 @@ '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 9717fa0ce6fd..15f8874d76ce 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -24,6 +24,7 @@ from __future__ import annotations +import re from datetime import datetime, date from typing import ( TYPE_CHECKING, @@ -42,6 +43,8 @@ EventStatus, EntityType, PrivacyLevel, + ScheduledEventRecurrenceFrequency, + ScheduledEventRecurrenceWeekday, try_enum ) from .mixins import Hashable @@ -74,10 +77,19 @@ ) # 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 @@ def weekdays(self) -> Optional[List[int]]: 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 @@ def count(self) -> Optional[int]: """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 @@ def year_days(self) -> Optional[List[int]]: 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 @@ def replace( 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 @@ def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: 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 @@ def _update(self, data: GuildScheduledEventPayload) -> None: 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 @@ async def edit( 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. From 2418b4cf3d6b8f57d678648985f13d2ed15f596d Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 7 Aug 2024 16:30:32 +0200 Subject: [PATCH 23/46] Docs things --- docs/api.rst | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index a8b7ee66edc6..4f84035785d2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3663,6 +3663,62 @@ of :class:`enum.Enum`. A burst reaction, also known as a "super reaction". +.. class:: ScheduledEventRecurrenceFrequency + + Represents the frequency of a scheduled event recurrence rule. + + .. versionadded:: 2.5 + + .. attribute:: yearly + + The event repeats yearly. + + .. attribute:: monthly + + The event repeats monthly. + + .. attribute:: weekly + + The event repeats weekly. + + .. attribute:: daily + + The event repeats daily. + + +.. class:: ScheduledEventRecurrenceWeekday + + Represents the available weekdays for a scheduled event recurrence rule. + + .. attribute:: monday + + The ``0`` weekday. + + .. attribute:: tuesday + + The ``1`` weekday. + + .. attribute:: wednesday + + The ``2`` weekday. + + .. attribute:: thursday + + The ``3`` weekday. + + .. attribute:: friday + + The ``4`` weekday. + + .. attribute:: saturday + + The ``5`` weekday. + + .. attribute:: sunday + + The ``6`` weekday. + + .. _discord-api-audit-logs: Audit Log Data From 647f6e2476a582a2f038ef18f15e249bcf38da91 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:23:30 +0200 Subject: [PATCH 24/46] Remove ScheduledEventRecurrenceRule.from_str and related --- discord/scheduled_event.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 15f8874d76ce..5763e209adaf 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -24,7 +24,6 @@ from __future__ import annotations -import re from datetime import datetime, date from typing import ( TYPE_CHECKING, @@ -77,15 +76,6 @@ ) # 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] @@ -349,25 +339,6 @@ def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: # 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. From 150c9e1024dfe8053a771da4ac0ec91206626bc4 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:24:48 +0200 Subject: [PATCH 25/46] Remove an enum from docs --- docs/api.rst | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 4f84035785d2..ad065ebeec1a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3686,39 +3686,6 @@ of :class:`enum.Enum`. The event repeats daily. -.. class:: ScheduledEventRecurrenceWeekday - - Represents the available weekdays for a scheduled event recurrence rule. - - .. attribute:: monday - - The ``0`` weekday. - - .. attribute:: tuesday - - The ``1`` weekday. - - .. attribute:: wednesday - - The ``2`` weekday. - - .. attribute:: thursday - - The ``3`` weekday. - - .. attribute:: friday - - The ``4`` weekday. - - .. attribute:: saturday - - The ``5`` weekday. - - .. attribute:: sunday - - The ``6`` weekday. - - .. _discord-api-audit-logs: Audit Log Data From 5ac7368e442ec7777ac02ca2c97b09ce22d8c30c Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:31:36 +0200 Subject: [PATCH 26/46] things --- discord/scheduled_event.py | 14 ++++++++------ docs/api.rst | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 5763e209adaf..330de7efad5a 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -96,8 +96,9 @@ class ScheduledEventRecurrenceRule: interval: :class:`int` The spacing between the events, defined by ``frequency``. - For example, a ``frequency`` of :attr:`ScheduledEventRecurrenceFrequency.weekly` and an ``interval`` of ``2`` - will result in an "Every other week" recurrence rule. + For example, a ``frequency`` of :attr:`ScheduledEventRecurrenceFrequency.weekly` and an + ``interval`` of :attr:`ScheduledEventRecurrenceFrequency.weekly` will result in an "Every + other week recurrence rule. weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] The weekdays the event will recur on. n_weekdays: Optional[List[Tuple[:class:`int`, :class:`ScheduledEVentRecurrenceWeekday`]]] @@ -174,6 +175,7 @@ def __init__( self._count: Optional[int] = None self._end: Optional[datetime] = None self._year_days: Optional[List[int]] = None + # We will be keeping the MISSING values for future use in _to_dict() self._weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = weekdays self._n_weekdays: Optional[List[_NWeekday]] = n_weekdays self._month_days: Optional[List[date]] = month_days @@ -288,12 +290,12 @@ def _to_dict(self) -> ScheduledEventRecurrenceRulePayload: by_year_day: Optional[List[int]] = None if self._weekdays not in (MISSING, None): - by_weekday = self._weekdays + by_weekday = [w.value for w in self._weekdays] if self._n_weekdays not in (MISSING, None): by_n_weekday = [ {'n': n, 'day': day} for n, day in self._n_weekdays - ] + ] # type: ignore if self._month_days not in (MISSING, None): by_month = [] @@ -309,7 +311,7 @@ def _to_dict(self) -> ScheduledEventRecurrenceRulePayload: return { 'start': self.start.isoformat(), 'end': self._end.isoformat() if self._end is not None else None, - 'frequency': self.frequency, + 'frequency': self.frequency.value, 'interval': self.interval, 'by_weekday': by_weekday, 'by_n_weekday': by_n_weekday, @@ -325,7 +327,7 @@ def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: start=parse_time(data['start']), frequency=data['frequency'], interval=data['interval'], - ) + ) # type: ignore self._count = data.get('count') self._year_days = data.get('by_year_day') diff --git a/docs/api.rst b/docs/api.rst index ad065ebeec1a..bfc08836be2c 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3686,6 +3686,42 @@ of :class:`enum.Enum`. The event repeats daily. +.. class:: ScheduledEventRecurrenceWeekday + + Represents the weekdays a scheduled event recurrence rule accepts. + + .. versionadded:: 2.5 + + .. attribute:: monday + + The ``0`` day of the week. + + .. attribute:: tuesday + + The ``1`` day of the week. + + .. attribute:: wednesday + + The ``2`` day of the week. + + .. attribute:: thursday + + The ``3`` day of the week. + + .. attribute:: friday + + The ``4`` day of the week. + + .. attribute:: saturday + + The ``5`` day of the week. + + .. attribute:: sunday + + + The ``6`` day of the week. + + .. _discord-api-audit-logs: Audit Log Data From a1988ba0a778506fb1fa165d976887177093e59c Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:34:13 +0200 Subject: [PATCH 27/46] Docstring fixes --- discord/scheduled_event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 330de7efad5a..0275dbc4d493 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -391,7 +391,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:`.ScheduledEventRecurrenceRule`] The recurrence rule for this event, or ``None``. .. versionadded:: 2.5 @@ -742,7 +742,7 @@ async def edit( 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_rule: Optional[:class:`.ScheduledEventRecurrence`] + recurrence_rule: Optional[:class:`.ScheduledEventRecurrenceRule`] The recurrence rule this event will follow, or `None` to set it to a one-time event. From b1a053e34084f322f435592feadd156f12e59bb2 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:50:46 +0200 Subject: [PATCH 28/46] _from_data finished...? --- discord/scheduled_event.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 0275dbc4d493..f75b16a2d77c 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -36,6 +36,7 @@ Literal, List, ) +from functools import partial from .asset import Asset from .enums import ( @@ -335,10 +336,29 @@ def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: 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 + wd_conv = partial(try_enum, ScheduledEventRecurrenceWeekday) + + raw_weekdays = data.get('by_weekday') + if raw_weekdays is not None: + self._weekdays = list(map(wd_conv, raw_weekdays)) + + raw_n_weekdays = data.get('by_n_weekday') + if raw_n_weekdays is not None: + self._n_weekdays = [_NWeekday(n['n'], wd_conv(n['day'])) for n in raw_n_weekdays] + + raw_months = data.get('by_month') + raw_month_days = data.get('by_month_day') + + if raw_months is not None and raw_month_days is not None: + self._month_days = [ + date( + 1900, # Using this as a placeholder year, ignored anyways + month, + day, + ) + for month, day in zip(raw_months, raw_month_days) + ] + return self From 1a3310a1bb87d2bb3cd1fe20e3332b86d04dd286 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:53:23 +0200 Subject: [PATCH 29/46] typo on docstring --- discord/scheduled_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index f75b16a2d77c..4657c4d9f15a 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -102,7 +102,7 @@ class ScheduledEventRecurrenceRule: other week recurrence rule. weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] The weekdays the event will recur on. - n_weekdays: Optional[List[Tuple[:class:`int`, :class:`ScheduledEVentRecurrenceWeekday`]]] + 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. From 3318ff23ae9dd6c35663d6c64018825bfc9964dc Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:54:40 +0200 Subject: [PATCH 30/46] Black --- discord/scheduled_event.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 4657c4d9f15a..3df8dccfc1ec 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -45,7 +45,7 @@ PrivacyLevel, ScheduledEventRecurrenceFrequency, ScheduledEventRecurrenceWeekday, - try_enum + try_enum, ) from .mixins import Hashable from .object import Object, OLDEST_OBJECT @@ -116,7 +116,6 @@ class ScheduledEventRecurrenceRule: '_weekdays', '_n_weekdays', '_month_days', - # Attributes that are returned by API only: '_count', '_end', @@ -294,9 +293,7 @@ def _to_dict(self) -> ScheduledEventRecurrenceRulePayload: by_weekday = [w.value for w in self._weekdays] if self._n_weekdays not in (MISSING, None): - by_n_weekday = [ - {'n': n, 'day': day} for n, day in self._n_weekdays - ] # type: ignore + by_n_weekday = [{'n': n, 'day': day} for n, day in self._n_weekdays] # type: ignore if self._month_days not in (MISSING, None): by_month = [] @@ -344,7 +341,7 @@ def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: raw_n_weekdays = data.get('by_n_weekday') if raw_n_weekdays is not None: - self._n_weekdays = [_NWeekday(n['n'], wd_conv(n['day'])) for n in raw_n_weekdays] + self._n_weekdays = [(n['n'], wd_conv(n['day'])) for n in raw_n_weekdays] # type: ignore raw_months = data.get('by_month') raw_month_days = data.get('by_month_day') From ca53a5cc4a6687a75b53091e863287b2dd24aa8d Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Wed, 28 Aug 2024 23:58:03 +0200 Subject: [PATCH 31/46] More black --- discord/guild.py | 10 +++++----- discord/http.py | 4 ++-- discord/types/scheduled_event.py | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 058ce323f16f..f56207f4a5aa 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3038,7 +3038,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3055,7 +3055,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3071,7 +3071,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3087,7 +3087,7 @@ async def create_scheduled_event( description: str = ..., image: bytes = ..., reason: Optional[str] = ..., - recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = ..., ) -> ScheduledEvent: ... @@ -3104,7 +3104,7 @@ async def create_scheduled_event( description: str = MISSING, image: bytes = MISSING, reason: Optional[str] = None, - recurrence_rule: Optional[ScheduledEventRecurrenceRule] = MISSING, + recurrence_rule: Optional[ScheduledEventRecurrenceRule] = MISSING, ) -> ScheduledEvent: r"""|coro| diff --git a/discord/http.py b/discord/http.py index 6b755b028b80..1d42995491a7 100644 --- a/discord/http.py +++ b/discord/http.py @@ -2032,7 +2032,7 @@ def create_guild_scheduled_event( 'description', 'entity_type', 'image', - 'recurrence_rule' + 'recurrence_rule', ) payload = {k: v for k, v in payload.items() if k in valid_keys} @@ -2086,7 +2086,7 @@ def edit_scheduled_event( 'description', 'entity_type', 'image', - 'recurrence_rule' + 'recurrence_rule', ) payload = {k: v for k, v in payload.items() if k in valid_keys} diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index 3d7aeec83bc4..30d93a357da4 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -34,9 +34,10 @@ EntityType = Literal[1, 2, 3] ScheduledEventRecurrenceRuleFrequency = Literal[0, 1, 2, 3] + class _NWeekday(TypedDict): - n: int - day: int + n: Literal[1, 2, 3, 4, 5] + day: Literal[0, 1, 2, 3, 4, 5, 6] class ScheduledEventRecurrenceRule(TypedDict): From 64f6515ec2e98d624820dbed434af71e474583bb Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 29 Aug 2024 12:49:34 +0200 Subject: [PATCH 32/46] what did i write here lmao --- discord/scheduled_event.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 3df8dccfc1ec..30bf047783b6 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -84,7 +84,7 @@ class _NWeekday(NamedTuple): class ScheduledEventRecurrenceRule: - r"""The recurrence rule for a scheduled event. + """The recurrence rule for a scheduled event. .. versionadded:: 2.5 @@ -98,7 +98,7 @@ class ScheduledEventRecurrenceRule: The spacing between the events, defined by ``frequency``. For example, a ``frequency`` of :attr:`ScheduledEventRecurrenceFrequency.weekly` and an - ``interval`` of :attr:`ScheduledEventRecurrenceFrequency.weekly` will result in an "Every + ``interval`` of ``2`` will result in an "Every other week recurrence rule. weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] The weekdays the event will recur on. From 390e9ac561162b0c6827f96e8aae8e48b7426d83 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 29 Aug 2024 13:24:54 +0200 Subject: [PATCH 33/46] Fixes --- discord/scheduled_event.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 30bf047783b6..b7e86d37eebe 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -29,8 +29,8 @@ TYPE_CHECKING, AsyncIterator, Dict, - NamedTuple, Optional, + Tuple, Union, overload, Literal, @@ -69,6 +69,8 @@ from .user import User GuildScheduledEventPayload = Union[BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCountPayload] + Week = Literal[1, 2, 3, 4, 5] + NWeekday = Tuple[Week, ScheduledEventRecurrenceWeekday] # fmt: off __all__ = ( @@ -78,10 +80,6 @@ # fmt: on -class _NWeekday(NamedTuple): - week: Literal[1, 2, 3, 4, 5] - day: ScheduledEventRecurrenceWeekday - class ScheduledEventRecurrenceRule: """The recurrence rule for a scheduled event. @@ -142,7 +140,7 @@ def __init__( frequency: ScheduledEventRecurrenceFrequency, interval: int, *, - n_weekdays: Optional[List[_NWeekday]], + n_weekdays: Optional[List[NWeekday]], ) -> None: ... @@ -166,7 +164,7 @@ def __init__( interval: int, *, weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = MISSING, - n_weekdays: Optional[List[_NWeekday]] = MISSING, + n_weekdays: Optional[List[NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> None: self.start: datetime = start @@ -177,7 +175,7 @@ def __init__( self._year_days: Optional[List[int]] = None # We will be keeping the MISSING values for future use in _to_dict() self._weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = weekdays - self._n_weekdays: Optional[List[_NWeekday]] = n_weekdays + self._n_weekdays: Optional[List[NWeekday]] = n_weekdays self._month_days: Optional[List[date]] = month_days def __repr__(self) -> str: @@ -197,7 +195,7 @@ def weekdays(self, new: Optional[List[ScheduledEventRecurrenceWeekday]]) -> None self._weekdays = new @property - def n_weekdays(self) -> Optional[List[_NWeekday]]: + def n_weekdays(self) -> Optional[List[NWeekday]]: """Optional[List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]]: Returns a read-only list of the N weekdays this event recurs on, or ``None``. """ @@ -206,7 +204,7 @@ def n_weekdays(self) -> Optional[List[_NWeekday]]: return self._n_weekdays.copy() @n_weekdays.setter - def n_weekdays(self, new: Optional[List[_NWeekday]]) -> None: + def n_weekdays(self, new: Optional[List[NWeekday]]) -> None: self._n_weekdays = new @property @@ -249,7 +247,7 @@ def replace( self, *, weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = MISSING, - n_weekdays: Optional[List[_NWeekday]] = MISSING, + n_weekdays: Optional[List[NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> Self: """Replaces and returns the recurrence rule with the same values except for the @@ -323,7 +321,7 @@ def _to_dict(self) -> ScheduledEventRecurrenceRulePayload: def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: self = cls( start=parse_time(data['start']), - frequency=data['frequency'], + frequency=try_enum(ScheduledEventRecurrenceFrequency, data['frequency']), interval=data['interval'], ) # type: ignore self._count = data.get('count') From 1f84efceda29cf3c9dedcf86147cc915cc986cee Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 29 Aug 2024 13:54:31 +0200 Subject: [PATCH 34/46] Final nits and examples in recurrence rule --- discord/scheduled_event.py | 105 +++++++++++++++++++++++++++---- discord/types/scheduled_event.py | 2 +- 2 files changed, 94 insertions(+), 13 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index b7e86d37eebe..d5cc77e41d05 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -95,15 +95,76 @@ class ScheduledEventRecurrenceRule: interval: :class:`int` The spacing between the events, defined by ``frequency``. - For example, a ``frequency`` of :attr:`ScheduledEventRecurrenceFrequency.weekly` and an - ``interval`` of ``2`` will result in an "Every - other week recurrence rule. + This must be ``1``, except when ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`, + in which case it can be set to ``2``. weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] The weekdays the event will recur on. + + Currently this only allows the following sets: + + If ``frequency`` is :attr`ScheduledEventRecurrenceFrequency.daily`: + + - Monday to Friday + - Tuesday to Saturday + - Sunday to Thursday + - Friday & Saturday + - Saturday & Sunday + - Sunday & Monday + + If ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`: + + The list length can only be up to 1, every day is valid. 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. + + Examples + -------- + + Creating a recurrence rule that repeats every weekday :: + + recurrence_rule = discord.ScheduledEventRecurrenceRule( + start=datetime.datetime(...), + frequency=discord.ScheduledEventRecurrenceFrequency.daily, + interval=1, + weekdays=[...], # Pass any valid set of weekdays in here. + ) + + Creating a recurrence rule that repeats every (other) Wednesday :: + + recurrence_rule = discord.ScheduledEventRecurrenceRule( + start=datetime.datetime(...), + frequency=discord.ScheduledEventRecurrenceFrequency.weekly, + interval=..., # Here you can either pass 1 or 2, if you pass 1 + # then the recurrence rule is "Every Wednesday", if you pass 2 + # then the recurrence rule is "Every other Wednesday, + weekdays=[...], # Only 1 item is allowed as frequency is weekly + ) + + Creating a recurrence rule that repeats monthly on the fourth Wednesday :: + + recurrence_rule = discord.ScheduledEventRecurrenceRule( + start=datetime.datetime(...), + frequency=discord.ScheduledEventRecurrenceFrequency.monthly, + interval=1, + n_weekdays=[(4, discord.ScheduledEventRecurrenceWeekday.wednesday)], + ) + + Creating a recurrence rule that repeats anually on July 24 :: + + recurrence_rule = discord.ScheduledEventRecurrenceRule( + start=datetime.datetime(...), + frequency=discord.ScheduledEventRecurrenceFrequency.yearly, + interval=1, + month_days=[ + datetime.date( + 1900, # This is a placeholder year, it is ignored so any value is valid + 7, # July + 24, # 24th + ) + ] + ) """ __slots__ = ( @@ -125,7 +186,10 @@ def __init__( self, /, start: datetime, - frequency: ScheduledEventRecurrenceFrequency, + frequency: Literal[ + ScheduledEventRecurrenceFrequency.daily, + ScheduledEventRecurrenceFrequency.weekly, + ], interval: int, *, weekdays: Optional[List[ScheduledEventRecurrenceWeekday]], @@ -137,7 +201,7 @@ def __init__( self, /, start: datetime, - frequency: ScheduledEventRecurrenceFrequency, + frequency: Literal[ScheduledEventRecurrenceFrequency.monthly], interval: int, *, n_weekdays: Optional[List[NWeekday]], @@ -149,7 +213,7 @@ def __init__( self, /, start: datetime, - frequency: ScheduledEventRecurrenceFrequency, + frequency: Literal[ScheduledEventRecurrenceFrequency.yearly], interval: int, *, month_days: Optional[List[date]], @@ -179,12 +243,12 @@ def __init__( self._month_days: Optional[List[date]] = month_days def __repr__(self) -> str: - return f'' + return f'' @property def weekdays(self) -> Optional[List[ScheduledEventRecurrenceWeekday]]: - """Optional[List[:class:`ScheduledEventRecurrenceWeekday`]]: Returns a read-only list of the weekdays this event - recurs on, or ``None``. + """Optional[List[:class:`ScheduledEventRecurrenceWeekday`]]: Returns a read-only list of the + weekdays this event recurs on, or ``None``. """ if self._weekdays in (MISSING, None): return None @@ -196,8 +260,8 @@ def weekdays(self, new: Optional[List[ScheduledEventRecurrenceWeekday]]) -> None @property def n_weekdays(self) -> Optional[List[NWeekday]]: - """Optional[List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]]: Returns a read-only - list of the N weekdays this event recurs on, or ``None``. + """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): return None @@ -339,7 +403,7 @@ def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: raw_n_weekdays = data.get('by_n_weekday') if raw_n_weekdays is not None: - self._n_weekdays = [(n['n'], wd_conv(n['day'])) for n in raw_n_weekdays] # type: ignore + self._n_weekdays = [(n['n'], wd_conv(n['day'])) for n in raw_n_weekdays] raw_months = data.get('by_month') raw_month_days = data.get('by_month_day') @@ -409,6 +473,15 @@ class ScheduledEvent(Hashable): recurrence_rule: Optional[:class:`.ScheduledEventRecurrenceRule`] The recurrence rule for this event, or ``None``. + .. versionadded:: 2.5 + sku_ids: List[:class:`Object`] + A list of objects that represent the related SKUs of this event. + + .. versionadded:: 2.5 + exceptions: List[:class:`Object`] + A list of objects that represent the events on the recurrence rule of this event that + were cancelled. + .. versionadded:: 2.5 """ @@ -432,6 +505,8 @@ class ScheduledEvent(Hashable): 'creator_id', 'location', 'recurrence_rule', + 'sku_ids', + 'exceptions', ) def __init__(self, *, state: ConnectionState, data: GuildScheduledEventPayload) -> None: @@ -456,6 +531,12 @@ def _update(self, data: GuildScheduledEventPayload) -> None: recurrence_rule = data.get('recurrence_rule') self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(recurrence_rule) if recurrence_rule else None + sku_ids = data.get('sku_ids', []) + self.sku_ids: List[Object] = list(map(Object, sku_ids)) + + exceptions = data.get('guild_scheduled_events_exceptions', []) + self.exceptions: List[Object] = list(map(Object, exceptions)) + creator = data.get('creator') self.creator: Optional[User] = self._state.store_user(creator) if creator else None diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index 30d93a357da4..cac1d6dfc6f5 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -62,7 +62,7 @@ class _BaseGuildScheduledEvent(TypedDict): privacy_level: PrivacyLevel status: EventStatus auto_start: bool - guild_scheduled_events_exceptions: List[int] + guild_scheduled_events_exceptions: List[Snowflake] recurrence_rule: Optional[ScheduledEventRecurrenceRule] sku_ids: List[Snowflake] creator_id: NotRequired[Optional[Snowflake]] From f1c047075c22b0e83e1853fc59019458decc78b0 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 29 Aug 2024 13:57:23 +0200 Subject: [PATCH 35/46] Black --- discord/scheduled_event.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index d5cc77e41d05..d6008aeae626 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -80,7 +80,6 @@ # fmt: on - class ScheduledEventRecurrenceRule: """The recurrence rule for a scheduled event. @@ -101,7 +100,7 @@ class ScheduledEventRecurrenceRule: The weekdays the event will recur on. Currently this only allows the following sets: - + If ``frequency`` is :attr`ScheduledEventRecurrenceFrequency.daily`: - Monday to Friday From 9c53a271a027e1d21ee624c27f2d1cdad0e1f9f7 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 29 Aug 2024 14:03:38 +0200 Subject: [PATCH 36/46] Forgot a quote --- discord/scheduled_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index d6008aeae626..1ee6c90425ad 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -137,7 +137,7 @@ class ScheduledEventRecurrenceRule: frequency=discord.ScheduledEventRecurrenceFrequency.weekly, interval=..., # Here you can either pass 1 or 2, if you pass 1 # then the recurrence rule is "Every Wednesday", if you pass 2 - # then the recurrence rule is "Every other Wednesday, + # then the recurrence rule is "Every other Wednesday" weekdays=[...], # Only 1 item is allowed as frequency is weekly ) From 94c001a2e204d75e482d971b26138d2f188fbc10 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 29 Aug 2024 14:16:59 +0200 Subject: [PATCH 37/46] Add another " --- discord/guild.py | 2 +- discord/scheduled_event.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index 2740dee0160b..98a03d5f7e8c 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3152,7 +3152,7 @@ async def create_scheduled_event( reason: Optional[:class:`str`] The reason for creating this scheduled event. Shows up on the audit log. recurrence_rule: Optional[:class:`ScheduledEventRecurrenceRule`] - The recurrence rule this event will follow. If this is `None` then this is + The recurrence rule this event will follow. If this is ``None`` then this is a one-time event. Raises diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 1ee6c90425ad..b57b3c96f1b4 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -838,7 +838,7 @@ async def edit( reason: Optional[:class:`str`] The reason for editing the scheduled event. Shows up on the audit log. recurrence_rule: Optional[:class:`.ScheduledEventRecurrenceRule`] - The recurrence rule this event will follow, or `None` to set it to a + The recurrence rule this event will follow, or ``None`` to set it to a one-time event. Raises From 002f18c071d8b7c73c298448abbd0e8d3f463830 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Thu, 29 Aug 2024 14:33:43 +0200 Subject: [PATCH 38/46] Fix ScheduledEvent.users doc --- discord/scheduled_event.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index b57b3c96f1b4..4a02a3cdbf31 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -993,9 +993,8 @@ async def users( after: Optional[Snowflake] = None, oldest_first: bool = MISSING, ) -> AsyncIterator[User]: - """|coro| - - Retrieves all :class:`User` that are subscribed to this event. + """Returns an :term:`asynchronous iterator` representing the users that have subscribed to + this event. This requires :attr:`Intents.members` to get information about members other than yourself. @@ -1005,9 +1004,9 @@ async def users( HTTPException Retrieving the members failed. - Returns - -------- - List[:class:`User`] + Yields + ------ + :class:`User` All subscribed users of this event. """ From 3ed33998cc793cd7acbb63bb9536530a4f037f4f Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:42:00 +0100 Subject: [PATCH 39/46] chore: Optional[List[X]] -> List[X] --- discord/scheduled_event.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 4a02a3cdbf31..f0ec69dfbce6 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -96,7 +96,7 @@ class ScheduledEventRecurrenceRule: This must be ``1``, except when ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`, in which case it can be set to ``2``. - weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] + weekdays: List[:class:`ScheduledEventRecurrenceWeekday`] The weekdays the event will recur on. Currently this only allows the following sets: @@ -113,9 +113,9 @@ class ScheduledEventRecurrenceRule: If ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`: The list length can only be up to 1, every day is valid. - n_weekdays: Optional[List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]] + n_weekdays: 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`]] + month_days: List[:class:`datetime.date`] The months and month days the scheduled event will recur on. Examples @@ -191,7 +191,7 @@ def __init__( ], interval: int, *, - weekdays: Optional[List[ScheduledEventRecurrenceWeekday]], + weekdays: List[ScheduledEventRecurrenceWeekday], ) -> None: ... @@ -203,7 +203,7 @@ def __init__( frequency: Literal[ScheduledEventRecurrenceFrequency.monthly], interval: int, *, - n_weekdays: Optional[List[NWeekday]], + n_weekdays: List[NWeekday], ) -> None: ... @@ -215,7 +215,7 @@ def __init__( frequency: Literal[ScheduledEventRecurrenceFrequency.yearly], interval: int, *, - month_days: Optional[List[date]], + month_days: List[date], ) -> None: ... @@ -226,9 +226,9 @@ def __init__( frequency: ScheduledEventRecurrenceFrequency, interval: int, *, - weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = MISSING, - n_weekdays: Optional[List[NWeekday]] = MISSING, - month_days: Optional[List[date]] = MISSING, + weekdays: List[ScheduledEventRecurrenceWeekday] = MISSING, + n_weekdays: List[NWeekday] = MISSING, + month_days: List[date] = MISSING, ) -> None: self.start: datetime = start self.frequency: ScheduledEventRecurrenceFrequency = frequency From 74a459f4653d582f981a1e26a6a74084ee728930 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:28:44 +0100 Subject: [PATCH 40/46] chore: Update ScheduledEventRecurrenceRule implementation --- discord/scheduled_event.py | 408 +++++++++++-------------------- discord/types/scheduled_event.py | 2 +- 2 files changed, 139 insertions(+), 271 deletions(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index f0ec69dfbce6..14a8c3e0c2cf 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -71,6 +71,7 @@ GuildScheduledEventPayload = Union[BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCountPayload] Week = Literal[1, 2, 3, 4, 5] NWeekday = Tuple[Week, ScheduledEventRecurrenceWeekday] + WeekDay = Literal[0, 1, 2, 3, 4, 5, 6] # fmt: off __all__ = ( @@ -81,343 +82,210 @@ class ScheduledEventRecurrenceRule: - """The recurrence rule for a scheduled event. - - .. versionadded:: 2.5 + """Represents a :class:`ScheduledEvent`'s recurrence rule. Parameters ---------- - start: :class:`datetime.datetime` - The datetime when the recurrence interval starts. + start_date: :class:`datetime.datetime` + When will this recurrence rule start. frequency: :class:`ScheduledEventRecurrenceFrequency` - How often the event occurs. + The frequency on which the event will recur. interval: :class:`int` - The spacing between the events, defined by ``frequency``. - - This must be ``1``, except when ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`, - in which case it can be set to ``2``. - weekdays: List[:class:`ScheduledEventRecurrenceWeekday`] - The weekdays the event will recur on. - - Currently this only allows the following sets: - - If ``frequency`` is :attr`ScheduledEventRecurrenceFrequency.daily`: - - - Monday to Friday - - Tuesday to Saturday - - Sunday to Thursday - - Friday & Saturday - - Saturday & Sunday - - Sunday & Monday - - If ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`: - - The list length can only be up to 1, every day is valid. + The spacing between events, defined by ``frequency``. + weekdays: List[:class:`int`] + The days within a week the event will recur on. Must be between + 0 (Monday) and 6 (Sunday). n_weekdays: List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]] - A (week, weekday) tuple list of the N weekdays the event will recur on. - month_days: List[:class:`datetime.date`] - The months and month days the scheduled event will recur on. - - Examples - -------- - - Creating a recurrence rule that repeats every weekday :: + A (week, weekday) pairs list that represent the specific day within a + specific week the event will recur on. - recurrence_rule = discord.ScheduledEventRecurrenceRule( - start=datetime.datetime(...), - frequency=discord.ScheduledEventRecurrenceFrequency.daily, - interval=1, - weekdays=[...], # Pass any valid set of weekdays in here. - ) - - Creating a recurrence rule that repeats every (other) Wednesday :: - - recurrence_rule = discord.ScheduledEventRecurrenceRule( - start=datetime.datetime(...), - frequency=discord.ScheduledEventRecurrenceFrequency.weekly, - interval=..., # Here you can either pass 1 or 2, if you pass 1 - # then the recurrence rule is "Every Wednesday", if you pass 2 - # then the recurrence rule is "Every other Wednesday" - weekdays=[...], # Only 1 item is allowed as frequency is weekly - ) - - Creating a recurrence rule that repeats monthly on the fourth Wednesday :: - - recurrence_rule = discord.ScheduledEventRecurrenceRule( - start=datetime.datetime(...), - frequency=discord.ScheduledEventRecurrenceFrequency.monthly, - interval=1, - n_weekdays=[(4, discord.ScheduledEventRecurrenceWeekday.wednesday)], - ) + ``week`` must be between 1 and 5, representing the first and last week of a month + respectively. + ``weekday`` must be a :class:`ScheduledEventRecurrenceWeekday` enum member. + month_days: List[:class:`datetime.date`] + The specific days and months in which the event will recur on. The year will be ignored. - Creating a recurrence rule that repeats anually on July 24 :: - - recurrence_rule = discord.ScheduledEventRecurrenceRule( - start=datetime.datetime(...), - frequency=discord.ScheduledEventRecurrenceFrequency.yearly, - interval=1, - month_days=[ - datetime.date( - 1900, # This is a placeholder year, it is ignored so any value is valid - 7, # July - 24, # 24th - ) - ] - ) + Attributes + ---------- + end_date: Optional[:class:`datetime.datetime`] + The date on which this recurrence rule will stop. + count: Optional[:class:`int`] + The amount of times the event will recur before stopping. Will be ``None`` + if :attr:`ScheduledEventRecurrenceRule.end_date` is ``None``. """ - __slots__ = ( - # Attributes user can set: - 'start', - 'frequency', - 'interval', - '_weekdays', - '_n_weekdays', - '_month_days', - # Attributes that are returned by API only: - '_count', - '_end', - '_year_days', - ) - - @overload - def __init__( - self, - /, - start: datetime, - frequency: Literal[ - ScheduledEventRecurrenceFrequency.daily, - ScheduledEventRecurrenceFrequency.weekly, - ], - interval: int, - *, - weekdays: List[ScheduledEventRecurrenceWeekday], - ) -> None: - ... - - @overload - def __init__( - self, - /, - start: datetime, - frequency: Literal[ScheduledEventRecurrenceFrequency.monthly], - interval: int, - *, - n_weekdays: List[NWeekday], - ) -> None: - ... - - @overload - def __init__( - self, - /, - start: datetime, - frequency: Literal[ScheduledEventRecurrenceFrequency.yearly], - interval: int, - *, - month_days: List[date], - ) -> None: - ... - def __init__( self, - /, - start: datetime, + start_date: datetime, frequency: ScheduledEventRecurrenceFrequency, - interval: int, + interval: Literal[1, 2], *, - weekdays: List[ScheduledEventRecurrenceWeekday] = MISSING, + weekdays: List[WeekDay] = MISSING, n_weekdays: List[NWeekday] = MISSING, month_days: List[date] = MISSING, ) -> None: - self.start: datetime = start + self.start_date: datetime = start_date 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 - # We will be keeping the MISSING values for future use in _to_dict() - self._weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = weekdays - self._n_weekdays: Optional[List[NWeekday]] = n_weekdays - self._month_days: Optional[List[date]] = month_days + self.interval: Literal[1, 2] = interval - def __repr__(self) -> str: - return f'' + self.count: Optional[int] = None + self.end_date: Optional[datetime] = None + + self._weekdays: List[WeekDay] = weekdays + self._n_weekdays: List[NWeekday] = n_weekdays + self._month_days: List[date] = month_days + self._year_days: List[int] = MISSING @property - def weekdays(self) -> Optional[List[ScheduledEventRecurrenceWeekday]]: - """Optional[List[:class:`ScheduledEventRecurrenceWeekday`]]: Returns a read-only list of the - weekdays this event recurs on, or ``None``. + def weekdays(self) -> List[WeekDay]: + """List[:class:`int`]: Returns a read-only list containing all the specific + days within a week the event will recur on. """ - if self._weekdays in (MISSING, None): - return None + if self._weekdays is MISSING: + return [] return self._weekdays.copy() - @weekdays.setter - 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:`ScheduledEventRecurrenceWeekday`]]]: Returns a - read-only list of the N weekdays this event recurs on, or ``None``. + def n_weekdays(self) -> List[NWeekday]: + """List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]: Returns + a read-only list containing all the specific days within a specific week the + event will recur on. """ - if self._n_weekdays in (MISSING, None): - return None + if self._n_weekdays is MISSING: + return [] return self._n_weekdays.copy() - @n_weekdays.setter - def n_weekdays(self, new: Optional[List[NWeekday]]) -> None: - self._n_weekdays = new - @property - def month_days(self) -> Optional[List[date]]: - """Optional[List[:class:`datetime.date`]]: Returns a read-only list of the month days this - event recurs on, or ``None``. + def month_days(self) -> List[date]: + """List[:class:`datetime.date`]: Returns a read-only list containing all the + specific days within a specific month the event will recur on. """ - if self._month_days in (MISSING, None): - return None + if self._month_days is MISSING: + return [] return self._month_days.copy() - @month_days.setter - def month_days(self, new: Optional[List[date]]) -> None: - self._month_days = new - - @property - def end(self) -> Optional[datetime]: - """Optional[:class:`datetime.datetime`]: The ending time of the recurrence interval, - or ``None``. - """ - return self._end - - @property - def count(self) -> Optional[int]: - """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]]: - """Optional[List[:class:`int`]]: Returns a read-only list of the year days this - event recurs on, or ``None``. + def year_days(self) -> List[int]: + """List[:class:`int`]: Returns a read-only list containing the year days on which + this event will recur on (1-364). """ - if self._year_days is None: - return None + if self._year_days is MISSING: + return [] return self._year_days.copy() - def replace( + def edit( self, *, - weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = MISSING, + weekdays: Optional[List[WeekDay]] = MISSING, n_weekdays: Optional[List[NWeekday]] = MISSING, month_days: Optional[List[date]] = MISSING, ) -> Self: - """Replaces and returns the recurrence rule with the same values except for the - ones that are changed. + """Edits the current recurrence rule. Parameters ---------- - weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] - The new weekdays for the event to recur on. - 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. + weekdays: List[:class:`int`] + The weekdays the event will recur on. Must be between 0 (Monday) and 6 (Sunday). + n_weekdays: List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]] + A (week, weekday) pairs list that the event will recur on. + month_days: List[:class:`datetime.date`] + A list of :class:`datetime.date` objects that represent a specific day on a month + when the event will recur on. The year is ignored. Returns ------- :class:`ScheduledEventRecurrenceRule` - The recurrence rule with the replaced values. + The updated recurrence rule. """ if weekdays is not MISSING: - self._weekdays = weekdays + if weekdays is None: + self._weekdays = MISSING + else: + self._weekdays = weekdays if n_weekdays is not MISSING: - self._n_weekdays = n_weekdays + if n_weekdays is None: + self._n_weekdays = MISSING + else: + self._n_weekdays = n_weekdays if month_days is not MISSING: - self._month_days = month_days + if month_days is None: + self._month_days = MISSING + else: + self._month_days = month_days return self - def _to_dict(self) -> ScheduledEventRecurrenceRulePayload: + def _get_month_days_payload(self) -> Tuple[List[int], List[int]]: + months, days = map(list, zip(*((m.month, m.day) for m in self._month_days))) + return months, days + + def _parse_month_days_payload(self, months: List[int], days: List[int]) -> List[date]: + return [ + date(1900, month, day) + for month, day in zip(months, days) + ] - by_weekday: Optional[List[int]] = None - by_n_weekday: Optional[List[NWeekdayPayload]] = None - by_month: Optional[List[int]] = None - by_month_day: Optional[List[int]] = None - by_year_day: Optional[List[int]] = None + @classmethod + def from_data(cls, data: Optional[ScheduledEventRecurrenceRulePayload]) -> Optional[Self]: + if data is None: + return None + + start = parse_time(data['start']) + end = parse_time(data.get('end')) + + self = cls( + start_date=start, + frequency=try_enum(ScheduledEventRecurrenceFrequency, data['frequency']), + interval=int(data['interval']), # type: ignore + ) + self.end_date = end - if self._weekdays not in (MISSING, None): - by_weekday = [w.value for w in self._weekdays] + weekdays = data.get('by_weekday', MISSING) or MISSING + self._weekdays = weekdays - if self._n_weekdays not in (MISSING, None): - by_n_weekday = [{'n': n, 'day': day} for n, day in self._n_weekdays] # type: ignore + n_weekdays = data.get('by_n_weekday', []) or [] + self._n_weekdays = [ + (data['n'], try_enum(ScheduledEventRecurrenceWeekday, data['day'])) + for data in n_weekdays + ] - if self._month_days not in (MISSING, None): - by_month = [] - by_month_day = [] + months = data.get('by_month') + month_days = data.get('by_month_day') - for dt in self._month_days: - by_month.append(dt.month) - by_month_day.append(dt.day) + if months and month_days: + self._month_days = self._parse_month_days_payload(months, month_days) - if self.year_days is not None: - by_year_day = self.year_days + return self - return { - 'start': self.start.isoformat(), - 'end': self._end.isoformat() if self._end is not None else None, + def to_dict(self): + payload = { + 'start': self.start_date.isoformat(), 'frequency': self.frequency.value, 'interval': self.interval, - 'by_weekday': by_weekday, - 'by_n_weekday': by_n_weekday, - 'by_month': by_month, - 'by_month_day': by_month_day, - 'by_year_day': by_year_day, - 'count': self.count, + 'by_weekday': None, + 'by_n_weekday': None, + 'by_month': None, + 'by_month_day': None, } - @classmethod - def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: - self = cls( - start=parse_time(data['start']), - frequency=try_enum(ScheduledEventRecurrenceFrequency, data['frequency']), - interval=data['interval'], - ) # type: ignore - 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) - - wd_conv = partial(try_enum, ScheduledEventRecurrenceWeekday) - - raw_weekdays = data.get('by_weekday') - if raw_weekdays is not None: - self._weekdays = list(map(wd_conv, raw_weekdays)) - - raw_n_weekdays = data.get('by_n_weekday') - if raw_n_weekdays is not None: - self._n_weekdays = [(n['n'], wd_conv(n['day'])) for n in raw_n_weekdays] - - raw_months = data.get('by_month') - raw_month_days = data.get('by_month_day') - - if raw_months is not None and raw_month_days is not None: - self._month_days = [ - date( - 1900, # Using this as a placeholder year, ignored anyways - month, - day, - ) - for month, day in zip(raw_months, raw_month_days) - ] + if self._weekdays is not MISSING: + payload['by_weekday'] = self._weekdays - return self + if self._n_weekdays is not MISSING: + payload['by_n_weekday'] = list( + map( + lambda nw: {'n': nw[0], 'day': nw[1].value}, + self._n_weekdays, + ), + ) + + if self._month_days is not MISSING: + months, month_days = self._get_month_days_payload() + payload['by_month'] = months + payload['by_month_day'] = month_days class ScheduledEvent(Hashable): @@ -527,8 +395,8 @@ def _update(self, data: GuildScheduledEventPayload) -> None: self.user_count: int = data.get('user_count', 0) self.creator_id: Optional[int] = _get_as_snowflake(data, 'creator_id') - recurrence_rule = data.get('recurrence_rule') - self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(recurrence_rule) if recurrence_rule else None + rrule = data.get('recurrence_rule') + self.recurrence_rule = ScheduledEventRecurrenceRule.from_data(rrule) sku_ids = data.get('sku_ids', []) self.sku_ids: List[Object] = list(map(Object, sku_ids)) @@ -952,7 +820,7 @@ async def edit( if recurrence_rule is not MISSING: if recurrence_rule is not None: - payload['recurrence_rule'] = recurrence_rule._to_dict() + payload['recurrence_rule'] = recurrence_rule.to_dict() else: payload['recurrence_rule'] = None diff --git a/discord/types/scheduled_event.py b/discord/types/scheduled_event.py index cac1d6dfc6f5..524897c2467b 100644 --- a/discord/types/scheduled_event.py +++ b/discord/types/scheduled_event.py @@ -45,7 +45,7 @@ class ScheduledEventRecurrenceRule(TypedDict): end: Optional[str] frequency: ScheduledEventRecurrenceRuleFrequency interval: int - by_weekday: Optional[List[int]] + by_weekday: Optional[List[Literal[0, 1, 2, 3, 4, 5, 6]]] by_n_weekday: Optional[List[_NWeekday]] by_month: Optional[List[int]] by_month_day: Optional[List[int]] From 990bc76ec9730070b82ae00eed3fdcd94a64317a Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:32:45 +0100 Subject: [PATCH 41/46] chore: Fix lint --- discord/guild.py | 2 +- discord/scheduled_event.py | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/discord/guild.py b/discord/guild.py index fbed39771b7c..94d8d1cc6c94 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -3412,7 +3412,7 @@ async def create_scheduled_event( if recurrence_rule is not MISSING: if recurrence_rule is not None: - payload['recurrence_rule'] = recurrence_rule._to_dict() + payload['recurrence_rule'] = recurrence_rule.to_dict() else: payload['recurrence_rule'] = None diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 14a8c3e0c2cf..ab9214dbd97e 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -36,7 +36,6 @@ Literal, List, ) -from functools import partial from .asset import Asset from .enums import ( @@ -58,7 +57,6 @@ GuildScheduledEvent as BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCount as GuildScheduledEventWithUserCountPayload, ScheduledEventRecurrenceRule as ScheduledEventRecurrenceRulePayload, - _NWeekday as NWeekdayPayload, EntityMetadata, ) @@ -223,10 +221,7 @@ def _get_month_days_payload(self) -> Tuple[List[int], List[int]]: return months, days def _parse_month_days_payload(self, months: List[int], days: List[int]) -> List[date]: - return [ - date(1900, month, day) - for month, day in zip(months, days) - ] + return [date(1900, month, day) for month, day in zip(months, days)] @classmethod def from_data(cls, data: Optional[ScheduledEventRecurrenceRulePayload]) -> Optional[Self]: @@ -247,10 +242,7 @@ def from_data(cls, data: Optional[ScheduledEventRecurrenceRulePayload]) -> Optio self._weekdays = weekdays n_weekdays = data.get('by_n_weekday', []) or [] - self._n_weekdays = [ - (data['n'], try_enum(ScheduledEventRecurrenceWeekday, data['day'])) - for data in n_weekdays - ] + self._n_weekdays = [(data['n'], try_enum(ScheduledEventRecurrenceWeekday, data['day'])) for data in n_weekdays] months = data.get('by_month') month_days = data.get('by_month_day') From ad24a590bfa5f52897ef4acff3d7bb4ef86af419 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:35:53 +0100 Subject: [PATCH 42/46] add return --- discord/scheduled_event.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index ab9214dbd97e..91308e3eb5e8 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -279,6 +279,8 @@ def to_dict(self): payload['by_month'] = months payload['by_month_day'] = month_days + return payload + class ScheduledEvent(Hashable): """Represents a scheduled event in a guild. From 1d7651704818c6ef5249fafabf84d3ee2d676129 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:39:31 +0100 Subject: [PATCH 43/46] chore: Update ScheduledEventRecurrenceWeekday docs --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index fd636c86fee7..93766b11e455 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3801,7 +3801,7 @@ of :class:`enum.Enum`. .. class:: ScheduledEventRecurrenceWeekday - Represents the weekdays a scheduled event recurrence rule accepts. + Represents a recurrence rule weekday. .. versionadded:: 2.5 From 4409dea4335ab85917389d40686124dca705131b Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:41:46 +0100 Subject: [PATCH 44/46] chore: Update `count` from API data --- discord/scheduled_event.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 91308e3eb5e8..544f0a4827a4 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -237,6 +237,7 @@ def from_data(cls, data: Optional[ScheduledEventRecurrenceRulePayload]) -> Optio interval=int(data['interval']), # type: ignore ) self.end_date = end + self.count = data.get('count') weekdays = data.get('by_weekday', MISSING) or MISSING self._weekdays = weekdays From 2a189c74692d664d213c9ffd5eae2834a73110c8 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:00:40 +0100 Subject: [PATCH 45/46] chore: Update docs and type annotations --- discord/enums.py | 11 ----- discord/scheduled_event.py | 82 ++++++++++++++++++++++++++++++++++---- docs/api.rst | 36 ----------------- 3 files changed, 75 insertions(+), 54 deletions(-) diff --git a/discord/enums.py b/discord/enums.py index cce6ac01dae0..f78f251e9196 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -78,7 +78,6 @@ 'SubscriptionStatus', 'MessageReferenceType', 'ScheduledEventRecurrenceFrequency', - 'ScheduledEventRecurrenceWeekday', ) @@ -871,16 +870,6 @@ class ScheduledEventRecurrenceFrequency(Enum): 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 544f0a4827a4..5b81f7acf50e 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -43,7 +43,6 @@ EntityType, PrivacyLevel, ScheduledEventRecurrenceFrequency, - ScheduledEventRecurrenceWeekday, try_enum, ) from .mixins import Hashable @@ -68,8 +67,8 @@ GuildScheduledEventPayload = Union[BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCountPayload] Week = Literal[1, 2, 3, 4, 5] - NWeekday = Tuple[Week, ScheduledEventRecurrenceWeekday] WeekDay = Literal[0, 1, 2, 3, 4, 5, 6] + NWeekday = Tuple[Week, WeekDay] # fmt: off __all__ = ( @@ -90,19 +89,30 @@ class ScheduledEventRecurrenceRule: The frequency on which the event will recur. interval: :class:`int` The spacing between events, defined by ``frequency``. + + Must be ``1`` except if ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`, + in which case it can also be ``2``. weekdays: List[:class:`int`] The days within a week the event will recur on. Must be between 0 (Monday) and 6 (Sunday). - n_weekdays: List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]] + + If ``frequency`` is ``2`` this can only have 1 item. + + This is mutally exclusive with ``n_weekdays`` and ``month_days``. + n_weekdays: List[Tuple[:class:`int`, :class:`int`]] A (week, weekday) pairs list that represent the specific day within a specific week the event will recur on. ``week`` must be between 1 and 5, representing the first and last week of a month respectively. - ``weekday`` must be a :class:`ScheduledEventRecurrenceWeekday` enum member. + ``weekday`` must be an integer between 0 (Monday) and 6 (Sunday). + + This is mutually exclusive with ``weekdays`` and ``month_days``. month_days: List[:class:`datetime.date`] The specific days and months in which the event will recur on. The year will be ignored. + This is mutually exclusive with ``weekdays`` and ``n_weekdays``. + Attributes ---------- end_date: Optional[:class:`datetime.datetime`] @@ -110,6 +120,64 @@ class ScheduledEventRecurrenceRule: count: Optional[:class:`int`] The amount of times the event will recur before stopping. Will be ``None`` if :attr:`ScheduledEventRecurrenceRule.end_date` is ``None``. + + Examples + -------- + + Creating a recurrence rule that repeats every weekday: :: + + rrule = discord.ScheduledEventRecurrenceRule( + start_date=..., + frequency=discord.ScheduledEventRecurrenceFrequency.daily, + interval=1, + weekdays=[0, 1, 2, 3, 4], # from monday to friday + ) + + Creating a recurrence rule that repeats every Wednesday: :: + + rrule = discord.ScheduledEventRecurrenceRule( + start_date=..., + frequency=discord.ScheduledEventRecurrenceFrequency.weekly, + interval=1, # interval must be 1 for the rule to be "every Wednesday" + weekdays=[2], # wednesday + ) + + Creating a recurrence rule that repeats every other Wednesday: :: + + rrule = discord.ScheduledEventRecurrenceRule( + start_date=..., + frequency=discord.ScheduledEventRecurrenceFrequency.weekly, + interval=2, # interval CAN ONLY BE 2 in this context, and makes the rule be "every other Wednesday" + weekdays=[2], + ) + + Creating a recurrence rule that repeats every monthly on the fourth Wednesday: :: + + rrule = discord.ScheduledEventRecurrenceRule( + start_date=..., + frequency=discord.ScheduledEventRecurrenceFrequency.monthly, + interval=1, + n_weekdays=[ + ( + 4, # fourth week + 2, # wednesday + ), + ], + ) + + Creating a recurrence rule that repeats anually on July 24: :: + + rrule = discord.ScheduledEventRecurrenceRule( + start_date=..., + frequency=discord.ScheduledEventRecurrenceFrequency.yearly, + month_days=[ + datetime.date( + year=1900, # use a placeholder year, it is ignored anyways + month=7, # July + day=24, # 24th + ), + ], + ) """ def __init__( @@ -184,7 +252,7 @@ def edit( ---------- weekdays: List[:class:`int`] The weekdays the event will recur on. Must be between 0 (Monday) and 6 (Sunday). - n_weekdays: List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]] + n_weekdays: List[Tuple[:class:`int`, :class:`int`]] A (week, weekday) pairs list that the event will recur on. month_days: List[:class:`datetime.date`] A list of :class:`datetime.date` objects that represent a specific day on a month @@ -243,7 +311,7 @@ def from_data(cls, data: Optional[ScheduledEventRecurrenceRulePayload]) -> Optio self._weekdays = weekdays n_weekdays = data.get('by_n_weekday', []) or [] - self._n_weekdays = [(data['n'], try_enum(ScheduledEventRecurrenceWeekday, data['day'])) for data in n_weekdays] + self._n_weekdays = [(data['n'], data['day']) for data in n_weekdays] months = data.get('by_month') month_days = data.get('by_month_day') @@ -270,7 +338,7 @@ def to_dict(self): if self._n_weekdays is not MISSING: payload['by_n_weekday'] = list( map( - lambda nw: {'n': nw[0], 'day': nw[1].value}, + lambda nw: {'n': nw[0], 'day': nw[1]}, self._n_weekdays, ), ) diff --git a/docs/api.rst b/docs/api.rst index 93766b11e455..78f0c6f8dce3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3799,42 +3799,6 @@ of :class:`enum.Enum`. The event repeats daily. -.. class:: ScheduledEventRecurrenceWeekday - - Represents a recurrence rule weekday. - - .. versionadded:: 2.5 - - .. attribute:: monday - - The ``0`` day of the week. - - .. attribute:: tuesday - - The ``1`` day of the week. - - .. attribute:: wednesday - - The ``2`` day of the week. - - .. attribute:: thursday - - The ``3`` day of the week. - - .. attribute:: friday - - The ``4`` day of the week. - - .. attribute:: saturday - - The ``5`` day of the week. - - .. attribute:: sunday - - - The ``6`` day of the week. - - .. class:: VoiceChannelEffectAnimationType Represents the animation type of a voice channel effect. From 4f11430cccf0bc1fe14d147101dda45a63799109 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:03:22 +0100 Subject: [PATCH 46/46] fix: reference to ScheduledEventRecurrenceWeekday --- discord/scheduled_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/scheduled_event.py b/discord/scheduled_event.py index 5b81f7acf50e..979de37d0e30 100644 --- a/discord/scheduled_event.py +++ b/discord/scheduled_event.py @@ -213,7 +213,7 @@ def weekdays(self) -> List[WeekDay]: @property def n_weekdays(self) -> List[NWeekday]: - """List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]: Returns + """List[Tuple[:class:`int`, :class:`int`]]: Returns a read-only list containing all the specific days within a specific week the event will recur on. """