Browse Source

Implement new capabilities, PASSIVE_UPDATE_V1, and add last_pin_timestamp to channels

pull/10109/head
dolfies 2 years ago
parent
commit
2da78bf2a6
  1. 30
      discord/channel.py
  2. 7
      discord/flags.py
  3. 6
      discord/gateway.py
  4. 15
      discord/guild.py
  5. 36
      discord/state.py
  6. 4
      discord/threads.py
  7. 13
      discord/types/gateway.py
  8. 2
      discord/types/voice.py

30
discord/channel.py

@ -153,6 +153,10 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
last_message_id: Optional[:class:`int`]
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
last_pin_timestamp: Optional[:class:`datetime.datetime`]
When the last pinned message was pinned. ``None`` if there are no pinned messages.
.. versionadded:: 2.0
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of ``0`` denotes that it is disabled.
@ -183,6 +187,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
'_overwrites',
'_type',
'last_message_id',
'last_pin_timestamp',
'default_auto_archive_duration',
'default_thread_slowmode_delay',
)
@ -218,6 +223,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
self.default_thread_slowmode_delay: int = data.get('default_thread_rate_limit_per_user', 0)
self._type: Literal[0, 5] = data.get('type', self._type)
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self.last_pin_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('last_pin_timestamp'))
self._fill_overwrites(data)
async def _get_channel(self) -> Self:
@ -883,6 +889,7 @@ class VocalGuildChannel(discord.abc.Messageable, discord.abc.Connectable, discor
'rtc_region',
'video_quality_mode',
'last_message_id',
'last_pin_timestamp',
)
def __init__(self, *, state: ConnectionState, guild: Guild, data: Union[VoiceChannelPayload, StageChannelPayload]):
@ -907,6 +914,7 @@ class VocalGuildChannel(discord.abc.Messageable, discord.abc.Connectable, discor
self.video_quality_mode: VideoQualityMode = try_enum(VideoQualityMode, data.get('video_quality_mode', 1))
self.category_id: Optional[int] = utils._get_as_snowflake(data, 'parent_id')
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self.last_pin_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('last_pin_timestamp'))
self.position: int = data['position']
self.slowmode_delay = data.get('rate_limit_per_user', 0)
self.bitrate: int = data['bitrate']
@ -1262,6 +1270,10 @@ class VoiceChannel(VocalGuildChannel):
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
.. versionadded:: 2.0
last_pin_timestamp: Optional[:class:`datetime.datetime`]
When the last pinned message was pinned. ``None`` if there are no pinned messages.
.. versionadded:: 2.0
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
@ -1462,6 +1474,10 @@ class StageChannel(VocalGuildChannel):
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
.. versionadded:: 2.0
last_pin_timestamp: Optional[:class:`datetime.datetime`]
When the last pinned message was pinned. ``None`` if there are no pinned messages.
.. versionadded:: 2.0
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
@ -2794,6 +2810,10 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
.. versionadded:: 2.0
last_pin_timestamp: Optional[:class:`datetime.datetime`]
When the last pinned message was pinned. ``None`` if there are no pinned messages.
.. versionadded:: 2.0
"""
@ -2802,6 +2822,7 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
'recipient',
'me',
'last_message_id',
'last_pin_timestamp',
'_message_request',
'_requested_at',
'_spam',
@ -2819,6 +2840,7 @@ class DMChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc.Pr
def _update(self, data: DMChannelPayload) -> None:
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self.last_pin_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('last_pin_timestamp'))
self._message_request: Optional[bool] = data.get('is_message_request')
self._requested_at: Optional[datetime.datetime] = utils.parse_time(data.get('is_message_request_timestamp'))
self._spam: bool = data.get('is_spam', False)
@ -3130,6 +3152,10 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
.. versionadded:: 2.0
last_pin_timestamp: Optional[:class:`datetime.datetime`]
When the last pinned message was pinned. ``None`` if there are no pinned messages.
.. versionadded:: 2.0
recipients: List[:class:`User`]
The users you are participating with in the group channel.
@ -3162,6 +3188,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
__slots__ = (
'last_message_id',
'last_pin_timestamp',
'id',
'recipients',
'owner_id',
@ -3188,6 +3215,7 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, discord.abc
self.name: Optional[str] = data.get('name')
self.recipients: List[User] = [self._state.store_user(u) for u in data.get('recipients', [])]
self.last_message_id: Optional[int] = utils._get_as_snowflake(data, 'last_message_id')
self.last_pin_timestamp: Optional[datetime.datetime] = utils.parse_time(data.get('last_pin_timestamp'))
self.managed: bool = data.get('managed', False)
self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id')
self.nicks: Dict[User, str] = {utils.get(self.recipients, id=int(k)): v for k, v in data.get('nicks', {}).items()} # type: ignore
@ -3606,6 +3634,7 @@ class PartialMessageable(discord.abc.Messageable, Hashable):
self.guild_id: Optional[int] = guild_id
self.type: Optional[ChannelType] = type
self.last_message_id: Optional[int] = None
self.last_pin_timestamp: Optional[datetime.datetime] = None
def __repr__(self) -> str:
return f'<{self.__class__.__name__} id={self.id} type={self.type!r}>'
@ -3649,7 +3678,6 @@ class PartialMessageable(discord.abc.Messageable, Hashable):
:class:`Permissions`
The resolved permissions.
"""
return Permissions.none()
def get_partial_message(self, message_id: int, /) -> PartialMessage:

7
discord/flags.py

@ -272,7 +272,7 @@ class Capabilities(BaseFlags):
@classmethod
def default(cls: Type[Self]) -> Self:
"""Returns a :class:`Capabilities` with the current value used by the library."""
return cls._from_value(2045)
return cls._from_value(8189)
@flag_value
def lazy_user_notes(self):
@ -335,6 +335,11 @@ class Capabilities(BaseFlags):
""":class:`bool`: Enable passive guild update (replace ``CHANNEL_UNREADS_UPDATE`` with ``PASSIVE_UPDATE_V1``, a similar event that includes a ``voice_states`` array and a ``members`` array that includes the members of aforementioned voice states)."""
return 1 << 11
@flag_value
def unknown_12(self):
""":class:`bool`: Unknown."""
return 1 << 12
@fill_with_flags(inverted=True)
class SystemChannelFlags(BaseFlags):

6
discord/gateway.py

@ -469,6 +469,7 @@ class DiscordWebSocket:
# presence['status'] = self._connection._status or 'unknown'
# presence['activities'] = self._connection._activities
# TODO: Implement client state
payload = {
'op': self.IDENTIFY,
'd': {
@ -478,10 +479,13 @@ class DiscordWebSocket:
'presence': presence,
'compress': not self._zlib_enabled, # We require at least one form of compression
'client_state': {
'guild_hashes': {},
'api_code_version': 0,
'guild_versions': {},
'highest_last_message_id': '0',
'private_channels_version': '0',
'read_state_version': 0,
'user_guild_settings_version': -1,
'user_settings_version': -1,
},
},
}

15
discord/guild.py

@ -500,9 +500,6 @@ class Guild(Hashable):
stage_instance = StageInstance(guild=self, data=s, state=state)
self._stage_instances[stage_instance.id] = stage_instance
for vs in guild.get('voice_states', []):
self._update_voice_state(vs, int(vs['channel_id']))
for s in guild.get('guild_scheduled_events', []):
scheduled_event = ScheduledEvent(data=s, state=state)
self._scheduled_events[scheduled_event.id] = scheduled_event
@ -549,13 +546,13 @@ class Guild(Hashable):
if (counts := guild.get('application_command_counts')) is not None:
self.command_counts = CommandCounts(counts.get(0, 0), counts.get(1, 0), counts.get(2, 0))
for vs in guild.get('voice_states', []):
self._update_voice_state(vs, int(vs['channel_id']))
cache_flags = state.member_cache_flags
if cache_flags.other:
for mdata in guild.get('members', []):
try:
member = Member(data=mdata, guild=self, state=state)
except KeyError:
continue
for mdata in guild.get('members', []):
member = Member(data=mdata, guild=self, state=state)
if cache_flags.joined or member.id == state.self_id or (cache_flags.voice and member.id in self._voice_states):
self._add_member(member)
for presence in guild.get('presences', []):

36
discord/state.py

@ -600,6 +600,7 @@ class ConnectionState:
self.analytics_token: Optional[str] = None
self.preferred_regions: List[str] = []
self.country_code: Optional[str] = None
self.api_code_version: int = 0
self.session_type: Optional[str] = None
self.auth_session_id: Optional[str] = None
self._emojis: Dict[int, Emoji] = {}
@ -977,6 +978,9 @@ class ConnectionState:
for presence in merged_presences:
presence['user'] = {'id': presence['user_id']} # type: ignore # :(
if 'properties' in guild_data:
guild_data.update(guild_data.pop('properties')) # type: ignore # :(
voice_states = guild_data.setdefault('voice_states', [])
voice_states.extend(guild_extra.get('voice_states', []))
members = guild_data.setdefault('members', [])
@ -1036,6 +1040,7 @@ class ConnectionState:
}
self.consents = TrackingSettings(data=data.get('consents', {}), state=self)
self.country_code = data.get('country_code', 'US')
self.api_code_version = data.get('api_code_version', 1)
self.session_type = data.get('session_type', 'normal')
self.auth_session_id = data.get('auth_session_id_hash')
self.connections = {c['id']: Connection(state=self, data=c) for c in data.get('connected_accounts', [])}
@ -1059,6 +1064,31 @@ class ConnectionState:
def parse_resumed(self, data: gw.ResumedEvent) -> None:
self.dispatch('resumed')
def parse_passive_update_v1(self, data: gw.PassiveUpdateEvent) -> None:
# PASSIVE_UPDATE_V1 is sent for large guilds you are not subscribed to
# in order to keep their read and voice states up-to-date; it replaces CHANNEL_UNREADS_UPDATE
guild = self._get_guild(int(data['guild_id']))
if not guild:
_log.debug('PASSIVE_UPDATE_V1 referencing an unknown guild ID: %s. Discarding.', data['guild_id'])
return
for channel_data in data.get('channels', []):
channel = guild.get_channel(int(channel_data['id']))
if not channel:
continue
channel.last_message_id = utils._get_as_snowflake(channel_data, 'last_message_id') # type: ignore
if 'last_pin_timestamp' in channel_data and hasattr(channel, 'last_pin_timestamp'):
channel.last_pin_timestamp = utils.parse_time(channel_data['last_pin_timestamp']) # type: ignore
guild._voice_states = {}
members = {int(m['user']['id']): m for m in data.get('members', [])}
for voice_state in data.get('voice_states', []):
user_id = int(voice_state['user_id'])
member = members.get(user_id)
if member:
voice_state['member'] = member
guild._update_voice_state(voice_state, utils._get_as_snowflake(voice_state, 'channel_id'))
def parse_message_create(self, data: gw.MessageCreateEvent) -> None:
guild_id = utils._get_as_snowflake(data, 'guild_id')
channel, _ = self._get_guild_channel(data)
@ -1529,6 +1559,8 @@ class ConnectionState:
return
last_pin = utils.parse_time(data.get('last_pin_timestamp'))
if hasattr(channel, 'last_pin_timestamp'):
channel.last_pin_timestamp = last_pin # type: ignore # Handled above
if guild is None:
self.dispatch('private_channel_pins_update', channel, last_pin)
@ -2193,8 +2225,10 @@ class ConnectionState:
self.dispatch('guild_join', guild)
def parse_guild_create(self, data: gw.GuildCreateEvent):
guild = self._get_create_guild(data)
if 'properties' in data:
data.update(data.pop('properties')) # type: ignore
guild = self._get_create_guild(data)
if guild is None:
return

4
discord/threads.py

@ -100,6 +100,8 @@ class Thread(Messageable, Hashable):
last_message_id: Optional[:class:`int`]
The last message ID of the message sent to this thread. It may
*not* point to an existing or valid message.
last_pin_timestamp: Optional[:class:`datetime.datetime`]
When the last pinned message was pinned. ``None`` if there are no pinned messages.
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this thread. A value of ``0`` denotes that it is disabled.
@ -133,6 +135,7 @@ class Thread(Messageable, Hashable):
'owner_id',
'parent_id',
'last_message_id',
'last_pin_timestamp',
'message_count',
'member_count',
'slowmode_delay',
@ -172,6 +175,7 @@ class Thread(Messageable, Hashable):
self.name: str = data['name']
self._type: ChannelType = try_enum(ChannelType, data['type'])
self.last_message_id: Optional[int] = _get_as_snowflake(data, 'last_message_id')
self.last_pin_timestamp: Optional[datetime] = parse_time(data.get('last_pin_timestamp'))
self.slowmode_delay: int = data.get('rate_limit_per_user', 0)
self.message_count: int = data['message_count']
self.member_count: int = data['member_count']

13
discord/types/gateway.py

@ -485,3 +485,16 @@ class AutoModerationActionExecution(TypedDict):
class GuildAuditLogEntryCreate(AuditLogEntry):
guild_id: Snowflake
class PartialUpdateChannel(TypedDict):
id: Snowflake
last_message_id: Optional[Snowflake]
last_pin_timestamp: NotRequired[Optional[str]]
class PassiveUpdateEvent(TypedDict):
guild_id: Snowflake
channels: List[PartialUpdateChannel]
voice_states: NotRequired[List[GuildVoiceState]]
members: NotRequired[List[MemberWithUser]]

2
discord/types/voice.py

@ -50,7 +50,7 @@ class GuildVoiceState(_VoiceState):
class VoiceState(_VoiceState, total=False):
channel_id: Optional[Snowflake]
channel_id: NotRequired[Optional[Snowflake]]
guild_id: NotRequired[Optional[Snowflake]]

Loading…
Cancel
Save