Browse Source

Run black

pull/10109/head
dolfies 3 years ago
parent
commit
957a484306
  1. 1
      discord/__init__.py
  2. 2
      discord/abc.py
  3. 23
      discord/activity.py
  4. 23
      discord/appinfo.py
  5. 17
      discord/calls.py
  6. 6
      discord/channel.py
  7. 29
      discord/client.py
  8. 37
      discord/commands.py
  9. 13
      discord/components.py
  10. 10
      discord/errors.py
  11. 4
      discord/ext/commands/context.py
  12. 55
      discord/gateway.py
  13. 6
      discord/guild.py
  14. 1
      discord/guild_folder.py
  15. 287
      discord/http.py
  16. 5
      discord/interactions.py
  17. 6
      discord/invite.py
  18. 9
      discord/iterators.py
  19. 5
      discord/modal.py
  20. 6
      discord/profile.py
  21. 24
      discord/settings.py
  22. 58
      discord/state.py
  23. 54
      discord/tracking.py
  24. 2
      discord/types/appinfo.py
  25. 22
      discord/user.py
  26. 17
      discord/utils.py
  27. 22
      discord/voice_client.py
  28. 4
      discord/welcome_screen.py

1
discord/__init__.py

@ -73,6 +73,7 @@ class _VersionInfo(NamedTuple):
releaselevel: Literal['alpha', 'beta', 'candidate', 'final'] releaselevel: Literal['alpha', 'beta', 'candidate', 'final']
serial: int serial: int
version_info: _VersionInfo = _VersionInfo(major=2, minor=0, micro=0, releaselevel='alpha', serial=2) version_info: _VersionInfo = _VersionInfo(major=2, minor=0, micro=0, releaselevel='alpha', serial=2)
logging.getLogger(__name__).addHandler(logging.NullHandler()) logging.getLogger(__name__).addHandler(logging.NullHandler())

2
discord/abc.py

@ -1778,7 +1778,7 @@ class Connectable(Protocol):
timeout: float = 60.0, timeout: float = 60.0,
reconnect: bool = True, reconnect: bool = True,
cls: Callable[[Client, Connectable], T] = MISSING, cls: Callable[[Client, Connectable], T] = MISSING,
_channel: Optional[Connectable] = None _channel: Optional[Connectable] = None,
) -> T: ) -> T:
"""|coro| """|coro|

23
discord/activity.py

@ -254,15 +254,15 @@ class Activity(BaseActivity):
def __eq__(self, other): def __eq__(self, other):
return ( return (
isinstance(other, Activity) and isinstance(other, Activity)
other.type == self.type and and other.type == self.type
other.name == self.name and and other.name == self.name
other.url == self.url and and other.url == self.url
other.emoji == self.emoji and and other.emoji == self.emoji
other.state == self.state and and other.state == self.state
other.session_id == self.session_id and and other.session_id == self.session_id
other.sync_id == self.sync_id and and other.sync_id == self.sync_id
other.start == self.start and other.start == self.start
) )
def __ne__(self, other): def __ne__(self, other):
@ -810,9 +810,9 @@ class CustomActivity(BaseActivity):
def to_settings_dict(self) -> Dict[str, Any]: def to_settings_dict(self) -> Dict[str, Any]:
o: Dict[str, Optional[Union[str, int]]] = {} o: Dict[str, Optional[Union[str, int]]] = {}
if (text := self.name): if text := self.name:
o['text'] = text o['text'] = text
if (emoji := self.emoji): if emoji := self.emoji:
o['emoji_name'] = emoji.name o['emoji_name'] = emoji.name
if emoji.id: if emoji.id:
o['emoji_id'] = emoji.id o['emoji_id'] = emoji.id
@ -880,6 +880,7 @@ def create_activity(data: Optional[ActivityPayload]) -> Optional[ActivityTypes]:
return Spotify(**data) return Spotify(**data)
return Activity(**data) return Activity(**data)
def create_settings_activity(*, data, state): def create_settings_activity(*, data, state):
if not data: if not data:
return return

23
discord/appinfo.py

@ -67,6 +67,7 @@ class ApplicationBot(User):
Whether the bot requires the completion of the full OAuth2 code Whether the bot requires the completion of the full OAuth2 code
grant flow to join. grant flow to join.
""" """
__slots__ = ('public', 'require_code_grant') __slots__ = ('public', 'require_code_grant')
def __init__(self, *, data, state: ConnectionState, application: Application): def __init__(self, *, data, state: ConnectionState, application: Application):
@ -179,7 +180,7 @@ class PartialApplication(Hashable):
'terms_of_service_url', 'terms_of_service_url',
'privacy_policy_url', 'privacy_policy_url',
'_icon', '_icon',
'_flags' '_flags',
'_cover_image', '_cover_image',
'public', 'public',
'require_code_grant', 'require_code_grant',
@ -213,10 +214,22 @@ class PartialApplication(Hashable):
self.tags: List[str] = data.get('tags', []) self.tags: List[str] = data.get('tags', [])
install_params = data.get('install_params', {}) install_params = data.get('install_params', {})
self.install_url = data.get('custom_install_url') if not install_params else utils.oauth_url(self.id, permissions=Permissions(int(install_params.get('permissions', 0))), scopes=install_params.get('scopes', utils.MISSING)) self.install_url = (
data.get('custom_install_url')
if not install_params
else utils.oauth_url(
self.id,
permissions=Permissions(int(install_params.get('permissions', 0))),
scopes=install_params.get('scopes', utils.MISSING),
)
)
self.public: bool = data.get('integration_public', data.get('bot_public', True)) # The two seem to be used interchangeably? self.public: bool = data.get(
self.require_code_grant: bool = data.get('integration_require_code_grant', data.get('bot_require_code_grant', False)) # Same here 'integration_public', data.get('bot_public', True)
) # The two seem to be used interchangeably?
self.require_code_grant: bool = data.get(
'integration_require_code_grant', data.get('bot_require_code_grant', False)
) # Same here
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r}>' return f'<{self.__class__.__name__} id={self.id} name={self.name!r} description={self.description!r}>'
@ -311,7 +324,7 @@ class Application(PartialApplication):
team: Optional[TeamPayload] = data.get('team') team: Optional[TeamPayload] = data.get('team')
self.team: Optional[Team] = Team(state, team) if team else None self.team: Optional[Team] = Team(state, team) if team else None
if (bot := data.get('bot')): if bot := data.get('bot'):
bot['public'] = data.get('bot_public', self.public) bot['public'] = data.get('bot_public', self.public)
bot['require_code_grant'] = data.get('bot_require_code_grant', self.require_code_grant) bot['require_code_grant'] = data.get('bot_require_code_grant', self.require_code_grant)
self.bot: Optional[ApplicationBot] = ApplicationBot(data=bot, state=state, application=self) if bot else None self.bot: Optional[ApplicationBot] = ApplicationBot(data=bot, state=state, application=self) if bot else None

17
discord/calls.py

@ -56,6 +56,7 @@ def _running_only(func: Callable):
raise ClientException('Call is over') raise ClientException('Call is over')
else: else:
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
return decorator return decorator
@ -75,9 +76,7 @@ class CallMessage:
The message associated with this call message. The message associated with this call message.
""" """
def __init__( def __init__(self, message: Message, *, participants: List[User], ended_timestamp: str) -> None:
self, message: Message, *, participants: List[User], ended_timestamp: str
) -> None:
self.message = message self.message = message
self.ended_timestamp = utils.parse_time(ended_timestamp) self.ended_timestamp = utils.parse_time(ended_timestamp)
self.participants = participants self.participants = participants
@ -175,9 +174,7 @@ class PrivateCall:
state = self.voice_state_for(user) state = self.voice_state_for(user)
return bool(state and state.channel and state.channel.id == self._channel_id) return bool(state and state.channel and state.channel.id == self._channel_id)
def _update( def _update(self, *, ringing: SnowflakeList = [], region: str = MISSING) -> None:
self, *, ringing: SnowflakeList = [], region: str = MISSING
) -> None:
if region is not MISSING: if region is not MISSING:
self.region = region self.region = region
channel = self.channel channel = self.channel
@ -206,7 +203,9 @@ class PrivateCall:
@property @property
def voice_states(self) -> Dict[int, VoiceState]: def voice_states(self) -> Dict[int, VoiceState]:
"""Mapping[:class:`int`, :class:`VoiceState`]: Returns a mapping of user IDs who have voice states in this call.""" """Mapping[:class:`int`, :class:`VoiceState`]: Returns a mapping of user IDs who have voice states in this call."""
return {k: v for k, v in self._state._voice_states.items() if bool(v and v.channel and v.channel.id == self._channel_id)} return {
k: v for k, v in self._state._voice_states.items() if bool(v and v.channel and v.channel.id == self._channel_id)
}
async def fetch_message(self) -> Optional[Message]: async def fetch_message(self) -> Optional[Message]:
"""|coro| """|coro|
@ -440,9 +439,7 @@ class GroupCall(PrivateCall):
if TYPE_CHECKING: if TYPE_CHECKING:
channel: GroupChannel channel: GroupChannel
def _update( def _update(self, *, ringing: List[int] = [], region: str = MISSING) -> None:
self, *, ringing: List[int] = [], region: str = MISSING
) -> None:
if region is not MISSING: if region is not MISSING:
self.region = region self.region = region

6
discord/channel.py

@ -2227,7 +2227,10 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
@overload @overload
async def edit( async def edit(
self, *, name: Optional[str] = ..., icon: Optional[bytes] = ..., self,
*,
name: Optional[str] = ...,
icon: Optional[bytes] = ...,
) -> Optional[GroupChannel]: ) -> Optional[GroupChannel]:
... ...
@ -2326,7 +2329,6 @@ class GroupChannel(discord.abc.Messageable, discord.abc.Connectable, Hashable):
return await super().connect(timeout=timeout, reconnect=reconnect, cls=cls) return await super().connect(timeout=timeout, reconnect=reconnect, cls=cls)
class PartialMessageable(discord.abc.Messageable, Hashable): class PartialMessageable(discord.abc.Messageable, Hashable):
"""Represents a partial messageable to aid with working messageable channels when """Represents a partial messageable to aid with working messageable channels when
only a channel ID are present. only a channel ID are present.

29
discord/client.py

@ -236,10 +236,7 @@ class Client:
connector, proxy=proxy, proxy_auth=proxy_auth, unsync_clock=unsync_clock, loop=self.loop connector, proxy=proxy, proxy_auth=proxy_auth, unsync_clock=unsync_clock, loop=self.loop
) )
self._handlers: Dict[str, Callable] = { self._handlers: Dict[str, Callable] = {'ready': self._handle_ready, 'connect': self._handle_connect}
'ready': self._handle_ready,
'connect': self._handle_connect
}
self._hooks: Dict[str, Callable] = { self._hooks: Dict[str, Callable] = {
'before_identify': self._call_before_identify_hook, 'before_identify': self._call_before_identify_hook,
@ -265,9 +262,15 @@ class Client:
# Internals # Internals
def _get_state(self, **options: Any) -> ConnectionState: def _get_state(self, **options: Any) -> ConnectionState:
return ConnectionState(dispatch=self.dispatch, handlers=self._handlers, return ConnectionState(
hooks=self._hooks, http=self.http, loop=self.loop, dispatch=self.dispatch,
client=self, **options) handlers=self._handlers,
hooks=self._hooks,
http=self.http,
loop=self.loop,
client=self,
**options,
)
def _handle_ready(self) -> None: def _handle_ready(self) -> None:
self._ready.set() self._ready.set()
@ -857,7 +860,7 @@ class Client:
The client may have multiple activities, these can be accessed under :attr:`activities`. The client may have multiple activities, these can be accessed under :attr:`activities`.
""" """
if (activities := self.activities): if activities := self.activities:
return activities[0] return activities[0]
@property @property
@ -1342,7 +1345,7 @@ class Client:
self_mute: bool = False, self_mute: bool = False,
self_deaf: bool = False, self_deaf: bool = False,
self_video: bool = False, self_video: bool = False,
preferred_region: Optional[str] = MISSING preferred_region: Optional[str] = MISSING,
) -> None: ) -> None:
"""|coro| """|coro|
@ -1380,11 +1383,7 @@ class Client:
# Guild stuff # Guild stuff
async def fetch_guilds( async def fetch_guilds(self, *, with_counts: bool = True) -> List[Guild]:
self,
*,
with_counts: bool = True
) -> List[Guild]:
"""Retrieves all your your guilds. """Retrieves all your your guilds.
.. note:: .. note::
@ -1700,7 +1699,7 @@ class Client:
state = self._connection state = self._connection
type = invite.type type = invite.type
if (message := invite._message): if message := invite._message:
kwargs = {'message': message} kwargs = {'message': message}
else: else:
kwargs = { kwargs = {

37
discord/commands.py

@ -98,7 +98,9 @@ class ApplicationCommand(Protocol):
state._interaction_cache[nonce] = (type.value, data['name'], acc_channel) state._interaction_cache[nonce] = (type.value, data['name'], acc_channel)
try: try:
await state.http.interact(type, data, acc_channel, form_data=True, nonce=nonce, application_id=self._application_id) await state.http.interact(
type, data, acc_channel, form_data=True, nonce=nonce, application_id=self._application_id
)
i = await state.client.wait_for( i = await state.client.wait_for(
'interaction_finish', 'interaction_finish',
check=lambda d: d.nonce == nonce, check=lambda d: d.nonce == nonce,
@ -145,9 +147,7 @@ class BaseCommand(ApplicationCommand):
'_default_member_permissions', '_default_member_permissions',
) )
def __init__( def __init__(self, *, state: ConnectionState, data: Dict[str, Any], channel: Optional[Messageable] = None) -> None:
self, *, state: ConnectionState, data: Dict[str, Any], channel: Optional[Messageable] = None
) -> None:
self._state = state self._state = state
self._data = data self._data = data
self.name = data['name'] self.name = data['name']
@ -180,7 +180,7 @@ class BaseCommand(ApplicationCommand):
def application(self): def application(self):
"""The application this command belongs to.""" """The application this command belongs to."""
... ...
#return self._state.get_application(self._application_id) # return self._state.get_application(self._application_id)
@property @property
def target_channel(self) -> Optional[Messageable]: def target_channel(self) -> Optional[Messageable]:
@ -193,6 +193,7 @@ class BaseCommand(ApplicationCommand):
@target_channel.setter @target_channel.setter
def target_channel(self, value: Optional[Messageable]) -> None: def target_channel(self, value: Optional[Messageable]) -> None:
from .abc import Messageable from .abc import Messageable
if not isinstance(value, Messageable) and value is not None: if not isinstance(value, Messageable) and value is not None:
raise TypeError('channel must derive from Messageable') raise TypeError('channel must derive from Messageable')
self._channel = value self._channel = value
@ -280,9 +281,7 @@ class UserCommand(BaseCommand):
super().__init__(**kwargs) super().__init__(**kwargs)
self._user = user self._user = user
async def __call__( async def __call__(self, user: Optional[Snowflake] = None, *, channel: Optional[Messageable] = None):
self, user: Optional[Snowflake] = None, *, channel: Optional[Messageable] = None
):
"""Use the user command. """Use the user command.
Parameters Parameters
@ -323,6 +322,7 @@ class UserCommand(BaseCommand):
@target_user.setter @target_user.setter
def target_user(self, value: Optional[Snowflake]) -> None: def target_user(self, value: Optional[Snowflake]) -> None:
from .abc import Snowflake from .abc import Snowflake
if not isinstance(value, Snowflake) and value is not None: if not isinstance(value, Snowflake) and value is not None:
raise TypeError('user must be Snowflake') raise TypeError('user must be Snowflake')
self._user = value self._user = value
@ -351,9 +351,7 @@ class MessageCommand(BaseCommand):
super().__init__(**kwargs) super().__init__(**kwargs)
self._message = message self._message = message
async def __call__( async def __call__(self, message: Optional[Message] = None, *, channel: Optional[Messageable] = None):
self, message: Optional[Message] = None, *, channel: Optional[Messageable] = None
):
"""Use the message command. """Use the message command.
Parameters Parameters
@ -394,6 +392,7 @@ class MessageCommand(BaseCommand):
@target_message.setter @target_message.setter
def target_message(self, value: Optional[Message]) -> None: def target_message(self, value: Optional[Message]) -> None:
from .message import Message from .message import Message
if not isinstance(value, Message) and value is not None: if not isinstance(value, Message) and value is not None:
raise TypeError('message must be Message') raise TypeError('message must be Message')
self._message = value self._message = value
@ -423,9 +422,7 @@ class SlashCommand(BaseCommand, SlashMixin):
__slots__ = ('_parent', 'options', 'children') __slots__ = ('_parent', 'options', 'children')
def __init__( def __init__(self, *, data: Dict[str, Any], **kwargs) -> None:
self, *, data: Dict[str, Any], **kwargs
) -> None:
super().__init__(data=data, **kwargs) super().__init__(data=data, **kwargs)
self._parent = self self._parent = self
self._unwrap_options(data.get('options', [])) self._unwrap_options(data.get('options', []))
@ -537,17 +534,21 @@ class SubCommand(SlashMixin):
if self.is_group(): if self.is_group():
raise TypeError('Cannot use a group') raise TypeError('Cannot use a group')
options = [{ options = [
{
'type': self._type.value, 'type': self._type.value,
'name': self.name, 'name': self.name,
'options': self._parse_kwargs(kwargs), 'options': self._parse_kwargs(kwargs),
}] }
]
for parent in self._walk_parents(): for parent in self._walk_parents():
options = [{ options = [
{
'type': parent._type.value, 'type': parent._type.value,
'name': parent.name, 'name': parent.name,
'options': options, 'options': options,
}] }
]
return await super().__call__(options, channel) return await super().__call__(options, channel)

13
discord/components.py

@ -293,7 +293,7 @@ class SelectMenu(Component):
return { return {
'compontent_type': self.type.value, 'compontent_type': self.type.value,
'custom_id': self.custom_id, 'custom_id': self.custom_id,
'values': [option.value for option in options] 'values': [option.value for option in options],
} }
async def choose(self, *options: SelectOption) -> Interaction: async def choose(self, *options: SelectOption) -> Interaction:
@ -460,7 +460,7 @@ class TextInput(Component):
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__ __repr_info__: ClassVar[Tuple[str, ...]] = __slots__
def __init__(self, data: TextInputPayload, _ = MISSING) -> None: def __init__(self, data: TextInputPayload, _=MISSING) -> None:
self.type: ComponentType = ComponentType.text_input self.type: ComponentType = ComponentType.text_input
self.style: TextStyle = try_enum(TextStyle, data['style']) self.style: TextStyle = try_enum(TextStyle, data['style'])
self.label: str = data['label'] self.label: str = data['label']
@ -489,8 +489,13 @@ class TextInput(Component):
@value.setter @value.setter
def value(self, value: Optional[str]) -> None: def value(self, value: Optional[str]) -> None:
length = len(value) if value is not None else 0 length = len(value) if value is not None else 0
if (self.required or value is not None) and ((self.min_length is not None and length < self.min_length) or (self.max_length is not None and length > self.max_length)): if (self.required or value is not None) and (
raise ValueError(f'value cannot be shorter than {self.min_length or 0} or longer than {self.max_length or "infinity"}') (self.min_length is not None and length < self.min_length)
or (self.max_length is not None and length > self.max_length)
):
raise ValueError(
f'value cannot be shorter than {self.min_length or 0} or longer than {self.max_length or "infinity"}'
)
self._answer = value self._answer = value

10
discord/errors.py

@ -55,6 +55,7 @@ class DiscordException(Exception):
Ideally speaking, this could be caught to handle any exceptions raised from this library. Ideally speaking, this could be caught to handle any exceptions raised from this library.
""" """
pass pass
@ -63,11 +64,13 @@ class ClientException(DiscordException):
These are usually for exceptions that happened due to user input. These are usually for exceptions that happened due to user input.
""" """
pass pass
class GatewayNotFound(DiscordException): class GatewayNotFound(DiscordException):
"""An exception that is raised when the gateway for Discord could not be found""" """An exception that is raised when the gateway for Discord could not be found"""
def __init__(self): def __init__(self):
message = 'The gateway to connect to Discord was not found.' message = 'The gateway to connect to Discord was not found.'
super().__init__(message) super().__init__(message)
@ -109,6 +112,7 @@ class HTTPException(DiscordException):
json: :class:`dict` json: :class:`dict`
The raw error JSON. The raw error JSON.
""" """
def __init__(self, response: _ResponseType, message: Optional[Union[str, Dict[str, Any]]]): def __init__(self, response: _ResponseType, message: Optional[Union[str, Dict[str, Any]]]):
self.response: _ResponseType = response self.response: _ResponseType = response
self.status: int = response.status # type: ignore - This attribute is filled by the library even if using requests self.status: int = response.status # type: ignore - This attribute is filled by the library even if using requests
@ -141,6 +145,7 @@ class Forbidden(HTTPException):
Subclass of :exc:`HTTPException` Subclass of :exc:`HTTPException`
""" """
pass pass
@ -149,6 +154,7 @@ class NotFound(HTTPException):
Subclass of :exc:`HTTPException` Subclass of :exc:`HTTPException`
""" """
pass pass
@ -159,6 +165,7 @@ class DiscordServerError(HTTPException):
.. versionadded:: 1.5 .. versionadded:: 1.5
""" """
pass pass
@ -166,6 +173,7 @@ class InvalidData(ClientException):
"""Exception that's raised when the library encounters unknown """Exception that's raised when the library encounters unknown
or invalid data from Discord. or invalid data from Discord.
""" """
pass pass
@ -174,6 +182,7 @@ class AuthFailure(ClientException):
fails to log you in from improper credentials or some other misc. fails to log you in from improper credentials or some other misc.
failure. failure.
""" """
pass pass
@ -191,6 +200,7 @@ class ConnectionClosed(ClientException):
reason: :class:`str` reason: :class:`str`
The reason provided for the closure. The reason provided for the closure.
""" """
def __init__(self, socket: ClientWebSocketResponse, *, code: Optional[int] = None): def __init__(self, socket: ClientWebSocketResponse, *, code: Optional[int] = None):
# This exception is just the same exception except # This exception is just the same exception except
# reconfigured to subclass ClientException for users # reconfigured to subclass ClientException for users

4
discord/ext/commands/context.py

@ -415,4 +415,6 @@ class Context(discord.abc.Messageable, Generic[BotT]):
applications: bool = True, applications: bool = True,
application: Optional[discord.abc.Snowflake] = None, application: Optional[discord.abc.Snowflake] = None,
): ):
return self.message.message_commands(query, limit=limit, command_ids=command_ids, applications=applications, application=application) return self.message.message_commands(
query, limit=limit, command_ids=command_ids, applications=applications, application=application
)

55
discord/gateway.py

@ -61,6 +61,7 @@ if TYPE_CHECKING:
class ReconnectWebSocket(Exception): class ReconnectWebSocket(Exception):
"""Signals to safely reconnect the websocket.""" """Signals to safely reconnect the websocket."""
def __init__(self, *, resume: bool = True): def __init__(self, *, resume: bool = True):
self.resume = resume self.resume = resume
self.op: str = 'RESUME' if resume else 'IDENTIFY' self.op: str = 'RESUME' if resume else 'IDENTIFY'
@ -433,20 +434,15 @@ class DiscordWebSocket:
'token': self.token, 'token': self.token,
'capabilities': 253, 'capabilities': 253,
'properties': self._super_properties, 'properties': self._super_properties,
'presence': { 'presence': {'status': 'online', 'since': 0, 'activities': [], 'afk': False},
'status': 'online',
'since': 0,
'activities': [],
'afk': False
},
'compress': False, 'compress': False,
'client_state': { 'client_state': {
'guild_hashes': {}, 'guild_hashes': {},
'highest_last_message_id': '0', 'highest_last_message_id': '0',
'read_state_version': 0, 'read_state_version': 0,
'user_guild_settings_version': -1 'user_guild_settings_version': -1,
} },
} },
} }
if not self._zlib_enabled: if not self._zlib_enabled:
@ -543,14 +539,12 @@ class DiscordWebSocket:
self._trace = trace = data.get('_trace', []) self._trace = trace = data.get('_trace', [])
self.sequence = msg['s'] self.sequence = msg['s']
self.session_id = data['session_id'] self.session_id = data['session_id']
_log.info('Connected to Gateway: %s (Session ID: %s).', _log.info('Connected to Gateway: %s (Session ID: %s).', ', '.join(trace), self.session_id)
', '.join(trace), self.session_id)
await self.voice_state() # Initial OP 4 await self.voice_state() # Initial OP 4
elif event == 'RESUMED': elif event == 'RESUMED':
self._trace = trace = data.get('_trace', []) self._trace = trace = data.get('_trace', [])
_log.info('Gateway has successfully RESUMED session %s under trace %s.', _log.info('Gateway has successfully RESUMED session %s under trace %s.', self.session_id, ', '.join(trace))
self.session_id, ', '.join(trace))
try: try:
func = self._discord_parsers[event] func = self._discord_parsers[event]
@ -663,7 +657,7 @@ class DiscordWebSocket:
activities: Optional[List[BaseActivity]] = None, activities: Optional[List[BaseActivity]] = None,
status: Optional[str] = None, status: Optional[str] = None,
since: float = 0.0, since: float = 0.0,
afk: bool = False afk: bool = False,
) -> None: ) -> None:
if activities is not None: if activities is not None:
if not all(isinstance(activity, BaseActivity) for activity in activities): if not all(isinstance(activity, BaseActivity) for activity in activities):
@ -675,26 +669,20 @@ class DiscordWebSocket:
if status == 'idle': if status == 'idle':
since = int(time.time() * 1000) since = int(time.time() * 1000)
payload = { payload = {'op': self.PRESENCE, 'd': {'activities': activities, 'afk': afk, 'since': since, 'status': str(status)}}
'op': self.PRESENCE,
'd': {
'activities': activities,
'afk': afk,
'since': since,
'status': str(status)
}
}
sent = utils._to_json(payload) sent = utils._to_json(payload)
_log.debug('Sending "%s" to change presence.', sent) _log.debug('Sending "%s" to change presence.', sent)
await self.send(sent) await self.send(sent)
async def request_lazy_guild(self, guild_id, *, typing=None, threads=None, activities=None, members=None, channels=None, thread_member_lists=None): async def request_lazy_guild(
self, guild_id, *, typing=None, threads=None, activities=None, members=None, channels=None, thread_member_lists=None
):
payload = { payload = {
'op': self.GUILD_SUBSCRIBE, 'op': self.GUILD_SUBSCRIBE,
'd': { 'd': {
'guild_id': str(guild_id), 'guild_id': str(guild_id),
} },
} }
data = payload['d'] data = payload['d']
@ -732,7 +720,7 @@ class DiscordWebSocket:
'limit': limit, 'limit': limit,
'presences': presences, 'presences': presences,
'user_ids': user_ids, 'user_ids': user_ids,
} },
} }
if nonce: if nonce:
@ -758,7 +746,7 @@ class DiscordWebSocket:
'self_mute': self_mute, 'self_mute': self_mute,
'self_deaf': self_deaf, 'self_deaf': self_deaf,
'self_video': self_video, 'self_video': self_video,
} },
} }
if preferred_region is not None: if preferred_region is not None:
@ -768,12 +756,7 @@ class DiscordWebSocket:
await self.send_as_json(payload) await self.send_as_json(payload)
async def access_dm(self, channel_id: int): async def access_dm(self, channel_id: int):
payload = { payload = {'op': self.CALL_CONNECT, 'd': {'channel_id': str(channel_id)}}
'op': self.CALL_CONNECT,
'd': {
'channel_id': str(channel_id)
}
}
_log.debug('Sending ACCESS_DM for channel %s.', channel_id) _log.debug('Sending ACCESS_DM for channel %s.', channel_id)
await self.send_as_json(payload) await self.send_as_json(payload)
@ -796,7 +779,7 @@ class DiscordWebSocket:
'd': { 'd': {
'guild_id': guild_id, 'guild_id': guild_id,
'type': type, 'type': type,
} },
} }
if nonce is not None: if nonce is not None:
@ -1014,8 +997,8 @@ class DiscordVoiceWebSocket:
else: else:
ssrc.speaking = speaking ssrc.speaking = speaking
#item = state.guild or state._state # item = state.guild or state._state
#item._update_speaking_status(user_id, speaking) # item._update_speaking_status(user_id, speaking)
await self._hook(self, msg) await self._hook(self, msg)

6
discord/guild.py

@ -3511,7 +3511,9 @@ class Guild(Hashable):
if self._state.is_guild_evicted(self): if self._state.is_guild_evicted(self):
raise ClientException('This guild is no longer available.') raise ClientException('This guild is no longer available.')
members = await self._state.scrape_guild(self, cache=cache, force_scraping=force_scraping, delay=delay, channels=channels) members = await self._state.scrape_guild(
self, cache=cache, force_scraping=force_scraping, delay=delay, channels=channels
)
if members is None: if members is None:
raise ClientException('Fetching members failed') raise ClientException('Fetching members failed')
return members # type: ignore return members # type: ignore
@ -3602,7 +3604,7 @@ class Guild(Hashable):
self_mute: bool = False, self_mute: bool = False,
self_deaf: bool = False, self_deaf: bool = False,
self_video: bool = False, self_video: bool = False,
preferred_region: Optional[str] = MISSING preferred_region: Optional[str] = MISSING,
) -> None: ) -> None:
"""|coro| """|coro|

1
discord/guild_folder.py

@ -57,6 +57,7 @@ class GuildFolder:
guilds: List[:class:`Guild`] guilds: List[:class:`Guild`]
The guilds in the folder. The guilds in the folder.
""" """
__slots__ = ('_state', 'id', 'name', '_colour', 'guilds') __slots__ = ('_state', 'id', 'name', '_colour', 'guilds')
def __init__(self, *, data, state: ConnectionState) -> None: def __init__(self, *, data, state: ConnectionState) -> None:

287
discord/http.py

@ -374,12 +374,14 @@ class HTTPClient:
'release_channel': 'stable', 'release_channel': 'stable',
'system_locale': 'en-US', 'system_locale': 'en-US',
'client_build_number': bn, 'client_build_number': bn,
'client_event_source': None 'client_event_source': None,
} }
self.encoded_super_properties = b64encode(json.dumps(sp).encode()).decode('utf-8') self.encoded_super_properties = b64encode(json.dumps(sp).encode()).decode('utf-8')
self._started = True self._started = True
async def ws_connect(self, url: str, *, compress: int = 0, host: Optional[str] = None) -> aiohttp.ClientWebSocketResponse: async def ws_connect(
self, url: str, *, compress: int = 0, host: Optional[str] = None
) -> aiohttp.ClientWebSocketResponse:
if not host: if not host:
host = url[6:].split('?')[0].rstrip('/') # Removes 'wss://' and the query params host = url[6:].split('?')[0].rstrip('/') # Removes 'wss://' and the query params
@ -430,7 +432,9 @@ class HTTPClient:
'Origin': 'https://discord.com', 'Origin': 'https://discord.com',
'Pragma': 'no-cache', 'Pragma': 'no-cache',
'Referer': 'https://discord.com/channels/@me', 'Referer': 'https://discord.com/channels/@me',
'Sec-CH-UA': '"Google Chrome";v="{0}", "Chromium";v="{0}", ";Not A Brand";v="99"'.format(self.browser_version.split('.')[0]), 'Sec-CH-UA': '"Google Chrome";v="{0}", "Chromium";v="{0}", ";Not A Brand";v="99"'.format(
self.browser_version.split('.')[0]
),
'Sec-CH-UA-Mobile': '?0', 'Sec-CH-UA-Mobile': '?0',
'Sec-CH-UA-Platform': '"Windows"', 'Sec-CH-UA-Platform': '"Windows"',
'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Dest': 'empty',
@ -439,7 +443,7 @@ class HTTPClient:
'User-Agent': self.user_agent, 'User-Agent': self.user_agent,
'X-Discord-Locale': 'en-US', 'X-Discord-Locale': 'en-US',
'X-Debug-Options': 'bugReporterEnabled', 'X-Debug-Options': 'bugReporterEnabled',
'X-Super-Properties': self.encoded_super_properties 'X-Super-Properties': self.encoded_super_properties,
} }
# Header modification # Header modification
@ -596,9 +600,7 @@ class HTTPClient:
self.ack_token = None self.ack_token = None
def get_me(self, with_analytics_token=True) -> Response[user.User]: def get_me(self, with_analytics_token=True) -> Response[user.User]:
params = { params = {'with_analytics_token': str(with_analytics_token).lower()}
'with_analytics_token': str(with_analytics_token).lower()
}
return self.request(Route('GET', '/users/@me'), params=params) return self.request(Route('GET', '/users/@me'), params=params)
async def static_login(self, token: str) -> user.User: async def static_login(self, token: str) -> user.User:
@ -673,32 +675,21 @@ class HTTPClient:
def send_typing(self, channel_id: Snowflake) -> Response[None]: def send_typing(self, channel_id: Snowflake) -> Response[None]:
return self.request(Route('POST', '/channels/{channel_id}/typing', channel_id=channel_id)) return self.request(Route('POST', '/channels/{channel_id}/typing', channel_id=channel_id))
async def ack_message( async def ack_message(self, channel_id: Snowflake, message_id: Snowflake): # TODO: response type (simple)
self, channel_id: Snowflake, message_id: Snowflake
): # TODO: response type (simple)
r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id) r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id)
payload = { payload = {'token': self.ack_token}
'token': self.ack_token
}
data = await self.request(r, json=payload) data = await self.request(r, json=payload)
self.ack_token = data['token'] self.ack_token = data['token']
def unack_message( def unack_message(self, channel_id: Snowflake, message_id: Snowflake, *, mention_count: int = 0) -> Response[None]:
self, channel_id: Snowflake, message_id: Snowflake, *, mention_count: int = 0
) -> Response[None]:
r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id) r = Route('POST', '/channels/{channel_id}/messages/{message_id}/ack', channel_id=channel_id, message_id=message_id)
payload = { payload = {'manual': True, 'mention_count': mention_count}
'manual': True,
'mention_count': mention_count
}
return self.request(r, json=payload) return self.request(r, json=payload)
def ack_messages(self, read_states) -> Response[None]: # TODO: type and implement def ack_messages(self, read_states) -> Response[None]: # TODO: type and implement
payload = { payload = {'read_states': read_states}
'read_states': read_states
}
return self.request(Route('POST', '/read-states/ack-bulk'), json=payload) return self.request(Route('POST', '/read-states/ack-bulk'), json=payload)
@ -916,7 +907,9 @@ class HTTPClient:
r = Route('PATCH', '/guilds/{guild_id}/voice-states/@me', guild_id=guild_id) r = Route('PATCH', '/guilds/{guild_id}/voice-states/@me', guild_id=guild_id)
return self.request(r, json=payload) return self.request(r, json=payload)
def edit_voice_state(self, guild_id: Snowflake, user_id: Snowflake, payload: Dict[str, Any]) -> Response[None]: # TODO: remove payload def edit_voice_state(
self, guild_id: Snowflake, user_id: Snowflake, payload: Dict[str, Any]
) -> Response[None]: # TODO: remove payload
r = Route('PATCH', '/guilds/{guild_id}/voice-states/{user_id}', guild_id=guild_id, user_id=user_id) r = Route('PATCH', '/guilds/{guild_id}/voice-states/{user_id}', guild_id=guild_id, user_id=user_id)
return self.request(r, json=payload) return self.request(r, json=payload)
@ -935,10 +928,12 @@ class HTTPClient:
r = Route('PATCH', '/guilds/{guild_id}/members/@me', guild_id=guild_id) r = Route('PATCH', '/guilds/{guild_id}/members/@me', guild_id=guild_id)
payload['avatar'] = avatar payload['avatar'] = avatar
else: else:
r = choice(( r = choice(
(
Route('PATCH', '/guilds/{guild_id}/members/@me/nick', guild_id=guild_id), Route('PATCH', '/guilds/{guild_id}/members/@me/nick', guild_id=guild_id),
Route('PATCH', '/guilds/{guild_id}/members/@me', guild_id=guild_id) Route('PATCH', '/guilds/{guild_id}/members/@me', guild_id=guild_id),
)) )
)
return self.request(r, json=payload, reason=reason) return self.request(r, json=payload, reason=reason)
@ -1024,9 +1019,7 @@ class HTTPClient:
return self.request(Route('POST', '/guilds/{guild_id}/channels', guild_id=guild_id), json=payload, reason=reason) return self.request(Route('POST', '/guilds/{guild_id}/channels', guild_id=guild_id), json=payload, reason=reason)
def delete_channel( def delete_channel(self, channel_id: Snowflake, *, reason: Optional[str] = None) -> Response[None]:
self, channel_id: Snowflake, *, reason: Optional[str] = None
) -> Response[None]:
return self.request(Route('DELETE', '/channels/{channel_id}', channel_id=channel_id), reason=reason) return self.request(Route('DELETE', '/channels/{channel_id}', channel_id=channel_id), reason=reason)
# Thread management # Thread management
@ -1085,29 +1078,25 @@ class HTTPClient:
def join_thread(self, channel_id: Snowflake) -> Response[None]: def join_thread(self, channel_id: Snowflake) -> Response[None]:
r = Route('POST', '/channels/{channel_id}/thread-members/@me', channel_id=channel_id) r = Route('POST', '/channels/{channel_id}/thread-members/@me', channel_id=channel_id)
params = { params = {'location': choice(('Banner', 'Toolbar Overflow', 'Context Menu'))}
'location': choice(('Banner', 'Toolbar Overflow', 'Context Menu'))
}
return self.request(r, params=params) return self.request(r, params=params)
def add_user_to_thread(self, channel_id: Snowflake, user_id: Snowflake) -> Response[None]: # TODO: Find a way to test private thread stuff def add_user_to_thread(
self, channel_id: Snowflake, user_id: Snowflake
) -> Response[None]: # TODO: Find a way to test private thread stuff
r = Route('PUT', '/channels/{channel_id}/thread-members/{user_id}', channel_id=channel_id, user_id=user_id) r = Route('PUT', '/channels/{channel_id}/thread-members/{user_id}', channel_id=channel_id, user_id=user_id)
return self.request(r) return self.request(r)
def leave_thread(self, channel_id: Snowflake) -> Response[None]: def leave_thread(self, channel_id: Snowflake) -> Response[None]:
r = Route('DELETE', '/channels/{channel_id}/thread-members/@me', channel_id=channel_id) r = Route('DELETE', '/channels/{channel_id}/thread-members/@me', channel_id=channel_id)
params = { params = {'location': choice(('Toolbar Overflow', 'Context Menu'))}
'location': choice(('Toolbar Overflow', 'Context Menu'))
}
return self.request(r, params=params) return self.request(r, params=params)
def remove_user_from_thread(self, channel_id: Snowflake, user_id: Snowflake) -> Response[None]: def remove_user_from_thread(self, channel_id: Snowflake, user_id: Snowflake) -> Response[None]:
r = Route('DELETE', '/channels/{channel_id}/thread-members/{user_id}', channel_id=channel_id, user_id=user_id) r = Route('DELETE', '/channels/{channel_id}/thread-members/{user_id}', channel_id=channel_id, user_id=user_id)
params = { params = {'location': 'Context Menu'}
'location': 'Context Menu'
}
return self.request(r, params=params) return self.request(r, params=params)
@ -1190,24 +1179,18 @@ class HTTPClient:
# Guild management # Guild management
def get_guilds(self, with_counts: bool = True) -> Response[List[guild.Guild]]: def get_guilds(self, with_counts: bool = True) -> Response[List[guild.Guild]]:
params = { params = {'with_counts': str(with_counts).lower()}
'with_counts': str(with_counts).lower()
}
return self.request(Route('GET', '/users/@me/guilds'), params=params, super_properties_to_track=True) return self.request(Route('GET', '/users/@me/guilds'), params=params, super_properties_to_track=True)
def leave_guild(self, guild_id: Snowflake, lurking: bool = False) -> Response[None]: def leave_guild(self, guild_id: Snowflake, lurking: bool = False) -> Response[None]:
r = Route('DELETE', '/users/@me/guilds/{guild_id}', guild_id=guild_id) r = Route('DELETE', '/users/@me/guilds/{guild_id}', guild_id=guild_id)
payload = { payload = {'lurking': lurking}
'lurking': lurking
}
return self.request(r, json=payload) return self.request(r, json=payload)
def get_guild(self, guild_id: Snowflake, with_counts: bool = True) -> Response[guild.Guild]: def get_guild(self, guild_id: Snowflake, with_counts: bool = True) -> Response[guild.Guild]:
params = { params = {'with_counts': str(with_counts).lower()}
'with_counts': str(with_counts).lower()
}
return self.request(Route('GET', '/guilds/{guild_id}', guild_id=guild_id), params=params) return self.request(Route('GET', '/guilds/{guild_id}', guild_id=guild_id), params=params)
@ -1225,7 +1208,7 @@ class HTTPClient:
'icon': icon, 'icon': icon,
'system_channel_id': None, 'system_channel_id': None,
'channels': [], 'channels': [],
'guild_template_code': template # API go brrr 'guild_template_code': template, # API go brrr
} }
return self.request(Route('POST', '/guilds'), json=payload) return self.request(Route('POST', '/guilds'), json=payload)
@ -1302,9 +1285,7 @@ class HTTPClient:
return self.request(Route('GET', '/guilds/{guild_id}/vanity-url', guild_id=guild_id)) return self.request(Route('GET', '/guilds/{guild_id}/vanity-url', guild_id=guild_id))
def change_vanity_code(self, guild_id: Snowflake, code: str, *, reason: Optional[str] = None) -> Response[None]: def change_vanity_code(self, guild_id: Snowflake, code: str, *, reason: Optional[str] = None) -> Response[None]:
payload = { payload = {'code': code}
'code': code
}
return self.request(Route('PATCH', '/guilds/{guild_id}/vanity-url', guild_id=guild_id), json=payload, reason=reason) return self.request(Route('PATCH', '/guilds/{guild_id}/vanity-url', guild_id=guild_id), json=payload, reason=reason)
@ -1483,7 +1464,9 @@ class HTTPClient:
return self.request(Route('GET', '/guilds/{guild_id}/member-verification', guild_id=guild_id), params=params) return self.request(Route('GET', '/guilds/{guild_id}/member-verification', guild_id=guild_id), params=params)
def accept_member_verification(self, guild_id: Snowflake, **payload) -> Response[None]: # payload is the same as the above return type def accept_member_verification(
self, guild_id: Snowflake, **payload
) -> Response[None]: # payload is the same as the above return type
return self.request(Route('PUT', '/guilds/{guild_id}/requests/@me', guild_id=guild_id), json=payload) return self.request(Route('PUT', '/guilds/{guild_id}/requests/@me', guild_id=guild_id), json=payload)
def get_all_integrations( def get_all_integrations(
@ -1535,9 +1518,7 @@ class HTTPClient:
action_type: Optional[AuditLogAction] = None, action_type: Optional[AuditLogAction] = None,
) -> Response[audit_log.AuditLog]: ) -> Response[audit_log.AuditLog]:
r = Route('GET', '/guilds/{guild_id}/audit-logs', guild_id=guild_id) r = Route('GET', '/guilds/{guild_id}/audit-logs', guild_id=guild_id)
params: Dict[str, Any] = { params: Dict[str, Any] = {'limit': limit}
'limit': limit
}
if before: if before:
params['before'] = before params['before'] = before
if after: if after:
@ -1578,15 +1559,23 @@ class HTTPClient:
guild_id=getattr(message.guild, 'id', None), guild_id=getattr(message.guild, 'id', None),
channel_id=message.channel.id, channel_id=message.channel.id,
channel_type=getattr(message.channel, 'type', None), channel_type=getattr(message.channel, 'type', None),
message_id=message.id message_id=message.id,
) )
elif type is InviteType.guild or type is InviteType.group_dm: # Join Guild, Accept Invite Page elif type is InviteType.guild or type is InviteType.group_dm: # Join Guild, Accept Invite Page
props = choice(( props = choice(
ContextProperties._from_accept_invite_page(guild_id=guild_id, channel_id=channel_id, channel_type=channel_type), (
ContextProperties._from_join_guild_popup(guild_id=guild_id, channel_id=channel_id, channel_type=channel_type) ContextProperties._from_accept_invite_page(
)) guild_id=guild_id, channel_id=channel_id, channel_type=channel_type
),
ContextProperties._from_join_guild_popup(
guild_id=guild_id, channel_id=channel_id, channel_type=channel_type
),
)
)
else: # Accept Invite Page else: # Accept Invite Page
props = ContextProperties._from_accept_invite_page(guild_id=guild_id, channel_id=channel_id, channel_type=channel_type) props = ContextProperties._from_accept_invite_page(
guild_id=guild_id, channel_id=channel_id, channel_type=channel_type
)
return self.request(Route('POST', '/invites/{invite_id}', invite_id=invite_id), context_properties=props, json={}) return self.request(Route('POST', '/invites/{invite_id}', invite_id=invite_id), context_properties=props, json={})
def create_invite( def create_invite(
@ -1751,24 +1740,18 @@ class HTTPClient:
return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason) return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason)
def ring(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]: def ring(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]:
payload = { payload = {'recipients': recipients or None}
'recipients': recipients or None
}
return self.request(Route('POST', '/channels/{channel_id}/call/ring', channel_id=channel_id), json=payload) return self.request(Route('POST', '/channels/{channel_id}/call/ring', channel_id=channel_id), json=payload)
def stop_ringing(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]: def stop_ringing(self, channel_id: Snowflake, *recipients: Snowflake) -> Response[None]:
r = Route('POST', '/channels/{channel_id}/call/stop-ringing', channel_id=channel_id) r = Route('POST', '/channels/{channel_id}/call/stop-ringing', channel_id=channel_id)
payload = { payload = {'recipients': recipients}
'recipients': recipients
}
return self.request(r, json=payload) return self.request(r, json=payload)
def change_call_voice_region(self, channel_id: int, voice_region: str): # TODO: return type def change_call_voice_region(self, channel_id: int, voice_region: str): # TODO: return type
payload = { payload = {'region': voice_region}
'region': voice_region
}
return self.request(Route('PATCH', '/channels/{channel_id}/call', channel_id=channel_id), json=payload) return self.request(Route('PATCH', '/channels/{channel_id}/call', channel_id=channel_id), json=payload)
@ -1997,51 +1980,65 @@ class HTTPClient:
def remove_relationship(self, user_id: Snowflake, *, action: RelationshipAction) -> Response[None]: def remove_relationship(self, user_id: Snowflake, *, action: RelationshipAction) -> Response[None]:
r = Route('DELETE', '/users/@me/relationships/{user_id}', user_id=user_id) r = Route('DELETE', '/users/@me/relationships/{user_id}', user_id=user_id)
if action is RelationshipAction.deny_request: # User Profile, Friends, DM Channel if action is RelationshipAction.deny_request: # User Profile, Friends, DM Channel
props = choice(( props = choice(
ContextProperties._from_friends_page(), ContextProperties._from_user_profile(), (
ContextProperties._from_dm_channel() ContextProperties._from_friends_page(),
)) ContextProperties._from_user_profile(),
ContextProperties._from_dm_channel(),
)
)
elif action is RelationshipAction.unfriend: # Friends, ContextMenu, User Profile, DM Channel elif action is RelationshipAction.unfriend: # Friends, ContextMenu, User Profile, DM Channel
props = choice(( props = choice(
ContextProperties._from_context_menu(), ContextProperties._from_user_profile(), (
ContextProperties._from_friends_page(), ContextProperties._from_dm_channel() ContextProperties._from_context_menu(),
)) ContextProperties._from_user_profile(),
ContextProperties._from_friends_page(),
ContextProperties._from_dm_channel(),
)
)
elif action == RelationshipAction.unblock: # Friends, ContextMenu, User Profile, DM Channel, NONE elif action == RelationshipAction.unblock: # Friends, ContextMenu, User Profile, DM Channel, NONE
props = choice(( props = choice(
ContextProperties._from_context_menu(), ContextProperties._from_user_profile(), (
ContextProperties._from_friends_page(), ContextProperties._from_dm_channel(), None ContextProperties._from_context_menu(),
)) ContextProperties._from_user_profile(),
ContextProperties._from_friends_page(),
ContextProperties._from_dm_channel(),
None,
)
)
elif action == RelationshipAction.remove_pending_request: # Friends elif action == RelationshipAction.remove_pending_request: # Friends
props = ContextProperties._from_friends_page() props = ContextProperties._from_friends_page()
return self.request(r, context_properties=props) # type: ignore return self.request(r, context_properties=props) # type: ignore
def add_relationship( def add_relationship(self, user_id: Snowflake, type: int = MISSING, *, action: RelationshipAction): # TODO: return type
self, user_id: Snowflake, type: int = MISSING, *, action: RelationshipAction
): # TODO: return type
r = Route('PUT', '/users/@me/relationships/{user_id}', user_id=user_id) r = Route('PUT', '/users/@me/relationships/{user_id}', user_id=user_id)
if action is RelationshipAction.accept_request: # User Profile, Friends, DM Channel if action is RelationshipAction.accept_request: # User Profile, Friends, DM Channel
props = choice(( props = choice(
(
ContextProperties._from_friends_page(), ContextProperties._from_friends_page(),
ContextProperties._from_user_profile(), ContextProperties._from_user_profile(),
ContextProperties._from_dm_channel() ContextProperties._from_dm_channel(),
)) )
)
elif action is RelationshipAction.block: # Friends, ContextMenu, User Profile, DM Channel. elif action is RelationshipAction.block: # Friends, ContextMenu, User Profile, DM Channel.
props = choice(( props = choice(
(
ContextProperties._from_context_menu(), ContextProperties._from_context_menu(),
ContextProperties._from_user_profile(), ContextProperties._from_user_profile(),
ContextProperties._from_friends_page(), ContextProperties._from_friends_page(),
ContextProperties._from_dm_channel() ContextProperties._from_dm_channel(),
)) )
)
elif action is RelationshipAction.send_friend_request: # ContextMenu, User Profile, DM Channel elif action is RelationshipAction.send_friend_request: # ContextMenu, User Profile, DM Channel
props = choice(( props = choice(
(
ContextProperties._from_context_menu(), ContextProperties._from_context_menu(),
ContextProperties._from_user_profile(), ContextProperties._from_user_profile(),
ContextProperties._from_dm_channel() ContextProperties._from_dm_channel(),
)) )
kwargs = { )
'context_properties': props # type: ignore kwargs = {'context_properties': props} # type: ignore
}
if type: if type:
kwargs['json'] = {'type': type} kwargs['json'] = {'type': type}
@ -2049,21 +2046,13 @@ class HTTPClient:
def send_friend_request(self, username, discriminator): # TODO: return type def send_friend_request(self, username, discriminator): # TODO: return type
r = Route('POST', '/users/@me/relationships') r = Route('POST', '/users/@me/relationships')
props = choice(( # Friends, Group DM props = choice((ContextProperties._from_add_friend_page, ContextProperties._from_group_dm)) # Friends, Group DM
ContextProperties._from_add_friend_page, payload = {'username': username, 'discriminator': int(discriminator)}
ContextProperties._from_group_dm
))
payload = {
'username': username,
'discriminator': int(discriminator)
}
return self.request(r, json=payload, context_properties=props) return self.request(r, json=payload, context_properties=props)
def change_friend_nickname(self, user_id, nickname): def change_friend_nickname(self, user_id, nickname):
payload = { payload = {'nickname': nickname}
'nickname': nickname
}
return self.request(Route('PATCH', '/users/@me/relationships/{user_id}', user_id=user_id), json=payload) return self.request(Route('PATCH', '/users/@me/relationships/{user_id}', user_id=user_id), json=payload)
@ -2083,10 +2072,10 @@ class HTTPClient:
def get_user(self, user_id: Snowflake) -> Response[user.User]: def get_user(self, user_id: Snowflake) -> Response[user.User]:
return self.request(Route('GET', '/users/{user_id}', user_id=user_id)) return self.request(Route('GET', '/users/{user_id}', user_id=user_id))
def get_user_profile(self, user_id: Snowflake, guild_id: Snowflake = MISSING, *, with_mutual_guilds: bool = True): # TODO: return type def get_user_profile(
params: Dict[str, Any] = { self, user_id: Snowflake, guild_id: Snowflake = MISSING, *, with_mutual_guilds: bool = True
'with_mutual_guilds': str(with_mutual_guilds).lower() ): # TODO: return type
} params: Dict[str, Any] = {'with_mutual_guilds': str(with_mutual_guilds).lower()}
if guild_id is not MISSING: if guild_id is not MISSING:
params['guild_id'] = guild_id params['guild_id'] = guild_id
@ -2102,16 +2091,12 @@ class HTTPClient:
return self.request(Route('GET', '/users/@me/notes/{user_id}', user_id=user_id)) return self.request(Route('GET', '/users/@me/notes/{user_id}', user_id=user_id))
def set_note(self, user_id: Snowflake, *, note: Optional[str] = None) -> Response[None]: def set_note(self, user_id: Snowflake, *, note: Optional[str] = None) -> Response[None]:
payload = { payload = {'note': note or ''}
'note': note or ''
}
return self.request(Route('PUT', '/users/@me/notes/{user_id}', user_id=user_id), json=payload) return self.request(Route('PUT', '/users/@me/notes/{user_id}', user_id=user_id), json=payload)
def change_hypesquad_house(self, house_id: int) -> Response[None]: def change_hypesquad_house(self, house_id: int) -> Response[None]:
payload = { payload = {'house_id': house_id}
'house_id': house_id
}
return self.request(Route('POST', '/hypesquad/online'), json=payload) return self.request(Route('POST', '/hypesquad/online'), json=payload)
@ -2143,9 +2128,7 @@ class HTTPClient:
return self.request(Route('GET', '/users/@me/connections/{type}/{id}/access-token', type=type, id=id)) return self.request(Route('GET', '/users/@me/connections/{type}/{id}/access-token', type=type, id=id))
def get_my_applications(self, *, with_team_applications: bool = True) -> Response[List[appinfo.AppInfo]]: def get_my_applications(self, *, with_team_applications: bool = True) -> Response[List[appinfo.AppInfo]]:
params = { params = {'with_team_applications': str(with_team_applications).lower()}
'with_team_applications': str(with_team_applications).lower()
}
return self.request(Route('GET', '/applications'), params=params, super_properties_to_track=True) return self.request(Route('GET', '/applications'), params=params, super_properties_to_track=True)
@ -2153,25 +2136,25 @@ class HTTPClient:
return self.request(Route('GET', '/applications/{app_id}', app_id=app_id), super_properties_to_track=True) return self.request(Route('GET', '/applications/{app_id}', app_id=app_id), super_properties_to_track=True)
def edit_application(self, app_id: Snowflake, payload) -> Response[appinfo.AppInfo]: def edit_application(self, app_id: Snowflake, payload) -> Response[appinfo.AppInfo]:
return self.request(Route('PATCH', '/applications/{app_id}', app_id=app_id), super_properties_to_track=True, json=payload) return self.request(
Route('PATCH', '/applications/{app_id}', app_id=app_id), super_properties_to_track=True, json=payload
)
def delete_application(self, app_id: Snowflake) -> Response[None]: def delete_application(self, app_id: Snowflake) -> Response[None]:
return self.request(Route('POST', '/applications/{app_id}/delete', app_id=app_id), super_properties_to_track=True) return self.request(Route('POST', '/applications/{app_id}/delete', app_id=app_id), super_properties_to_track=True)
def transfer_application(self, app_id: Snowflake, team_id: Snowflake) -> Response[appinfo.AppInfo]: def transfer_application(self, app_id: Snowflake, team_id: Snowflake) -> Response[appinfo.AppInfo]:
payload = { payload = {'team_id': team_id}
'team_id': team_id
}
return self.request(Route('POST', '/applications/{app_id}/transfer', app_id=app_id), json=payload, super_properties_to_track=True) return self.request(
Route('POST', '/applications/{app_id}/transfer', app_id=app_id), json=payload, super_properties_to_track=True
)
def get_partial_application(self, app_id: Snowflake) -> Response[appinfo.PartialAppInfo]: def get_partial_application(self, app_id: Snowflake) -> Response[appinfo.PartialAppInfo]:
return self.request(Route('GET', '/applications/{app_id}/rpc', app_id=app_id)) return self.request(Route('GET', '/oauth2/applications/{app_id}/rpc', app_id=app_id))
def create_app(self, name: str): def create_app(self, name: str):
payload = { payload = {'name': name}
'name': name
}
return self.request(Route('POST', '/applications'), json=payload, super_properties_to_track=True) return self.request(Route('POST', '/applications'), json=payload, super_properties_to_track=True)
@ -2183,20 +2166,17 @@ class HTTPClient:
self, app_id: Snowflake, *, localize: bool = False, with_bundled_skus: bool = True self, app_id: Snowflake, *, localize: bool = False, with_bundled_skus: bool = True
): # TODO: return type ): # TODO: return type
r = Route('GET', '/applications/{app_id}/skus', app_id=app_id) r = Route('GET', '/applications/{app_id}/skus', app_id=app_id)
params = { params = {'localize': str(localize).lower(), 'with_bundled_skus': str(with_bundled_skus).lower()}
'localize': str(localize).lower(),
'with_bundled_skus': str(with_bundled_skus).lower()
}
return self.request(r, params=params, super_properties_to_track=True) return self.request(r, params=params, super_properties_to_track=True)
def get_app_whitelist(self, app_id): def get_app_whitelist(self, app_id):
return self.request(Route('GET', '/oauth2/applications/{app_id}/allowlist', app_id=app_id), super_properties_to_track=True) return self.request(
Route('GET', '/oauth2/applications/{app_id}/allowlist', app_id=app_id), super_properties_to_track=True
)
def create_team(self, name: str): def create_team(self, name: str):
payload = { payload = {'name': name}
'name': name
}
return self.request(Route('POST', '/teams'), json=payload, super_properties_to_track=True) return self.request(Route('POST', '/teams'), json=payload, super_properties_to_track=True)
@ -2207,7 +2187,9 @@ class HTTPClient:
return self.request(Route('GET', '/teams/{team_id}', team_id=team_id), super_properties_to_track=True) return self.request(Route('GET', '/teams/{team_id}', team_id=team_id), super_properties_to_track=True)
def edit_team(self, team_id: Snowflake, payload) -> Response[team.Team]: def edit_team(self, team_id: Snowflake, payload) -> Response[team.Team]:
return self.request(Route('PATCH', '/teams/{team_id}', team_id=team_id), json=payload, super_properties_to_track=True) return self.request(
Route('PATCH', '/teams/{team_id}', team_id=team_id), json=payload, super_properties_to_track=True
)
def delete_team(self, team_id: Snowflake) -> Response[None]: def delete_team(self, team_id: Snowflake) -> Response[None]:
return self.request(Route('POST', '/teams/{app_id}/delete', team_id=team_id), super_properties_to_track=True) return self.request(Route('POST', '/teams/{app_id}/delete', team_id=team_id), super_properties_to_track=True)
@ -2219,15 +2201,17 @@ class HTTPClient:
return self.request(Route('GET', '/teams/{team_id}/members', team_id=team_id), super_properties_to_track=True) return self.request(Route('GET', '/teams/{team_id}/members', team_id=team_id), super_properties_to_track=True)
def invite_team_member(self, team_id: Snowflake, username: str, discriminator: Snowflake): def invite_team_member(self, team_id: Snowflake, username: str, discriminator: Snowflake):
payload = { payload = {'username': username, 'discriminator': str(discriminator)}
'username': username,
'discriminator': str(discriminator)
}
return self.request(Route('POST', '/teams/{team_id}/members', team_id=team_id), json=payload, super_properties_to_track=True) return self.request(
Route('POST', '/teams/{team_id}/members', team_id=team_id), json=payload, super_properties_to_track=True
)
def remove_team_member(self, team_id: Snowflake, user_id: Snowflake): def remove_team_member(self, team_id: Snowflake, user_id: Snowflake):
return self.request(Route('DELETE', '/teams/{team_id}/members/{user_id}', team_id=team_id, user_id=user_id), super_properties_to_track=True) return self.request(
Route('DELETE', '/teams/{team_id}/members/{user_id}', team_id=team_id, user_id=user_id),
super_properties_to_track=True,
)
def botify_app(self, app_id: Snowflake): def botify_app(self, app_id: Snowflake):
return self.request(Route('POST', '/applications/{app_id}/bot', app_id=app_id), super_properties_to_track=True) return self.request(Route('POST', '/applications/{app_id}/bot', app_id=app_id), super_properties_to_track=True)
@ -2241,12 +2225,7 @@ class HTTPClient:
def mobile_report( # Report v1 def mobile_report( # Report v1
self, guild_id: Snowflake, channel_id: Snowflake, message_id: Snowflake, reason: str self, guild_id: Snowflake, channel_id: Snowflake, message_id: Snowflake, reason: str
): # TODO: return type ): # TODO: return type
payload = { payload = {'guild_id': guild_id, 'channel_id': channel_id, 'message_id': message_id, 'reason': reason}
'guild_id': guild_id,
'channel_id': channel_id,
'message_id': message_id,
'reason': reason
}
return self.request(Route('POST', '/report'), json=payload) return self.request(Route('POST', '/report'), json=payload)
@ -2262,7 +2241,7 @@ class HTTPClient:
*, *,
form_data: bool = False, form_data: bool = False,
nonce: Optional[str] = MISSING, nonce: Optional[str] = MISSING,
application_id: Snowflake = MISSING application_id: Snowflake = MISSING,
) -> Response[None]: ) -> Response[None]:
state = getattr(message, '_state', channel._state) state = getattr(message, '_state', channel._state)
payload = { payload = {

5
discord/interactions.py

@ -118,9 +118,7 @@ class Interaction:
return cls(int(id), type, nonce, user=user, name=name, state=user._state, channel=channel) return cls(int(id), type, nonce, user=user, name=name, state=user._state, channel=channel)
@classmethod @classmethod
def _from_message( def _from_message(cls, message: Message, *, id: Snowflake, type: int, user: UserPayload, **data) -> Interaction:
cls, message: Message, *, id: Snowflake, type: int, user: UserPayload, **data
) -> Interaction:
state = message._state state = message._state
name = data.get('name') name = data.get('name')
user_cls = state.store_user(user) user_cls = state.store_user(user)
@ -142,6 +140,7 @@ class Interaction:
"""Optional[:class:`Message`]: Returns the message that is the response to this interaction. """Optional[:class:`Message`]: Returns the message that is the response to this interaction.
May not exist or be cached. May not exist or be cached.
""" """
def predicate(message: Message) -> bool: def predicate(message: Message) -> bool:
return message.interaction is not None and message.interaction.id == self.id return message.interaction is not None and message.interaction.id == self.id

6
discord/invite.py

@ -399,6 +399,7 @@ class Invite(Hashable):
application = data.get('target_application') application = data.get('target_application')
if application is not None: if application is not None:
from .appinfo import PartialApplication from .appinfo import PartialApplication
application = PartialApplication(data=application, state=state) application = PartialApplication(data=application, state=state)
self.target_application: Optional[PartialApplication] = application self.target_application: Optional[PartialApplication] = application
@ -552,7 +553,7 @@ class Invite(Hashable):
""" """
state = self._state state = self._state
type = self.type type = self.type
if (message := self._message): if message := self._message:
kwargs = {'message': message} kwargs = {'message': message}
else: else:
kwargs = { kwargs = {
@ -563,12 +564,15 @@ class Invite(Hashable):
data = await state.http.accept_invite(self.code, type, **kwargs) data = await state.http.accept_invite(self.code, type, **kwargs)
if type is InviteType.guild: if type is InviteType.guild:
from .guild import Guild from .guild import Guild
return Guild(data=data['guild'], state=state) return Guild(data=data['guild'], state=state)
elif type is InviteType.group_dm: elif type is InviteType.group_dm:
from .channel import GroupChannel from .channel import GroupChannel
return GroupChannel(data=data['channel'], state=state, me=state.user) # type: ignore return GroupChannel(data=data['channel'], state=state, me=state.user) # type: ignore
else: else:
from .user import User from .user import User
return User(data=data['inviter'], state=state) return User(data=data['inviter'], state=state)
async def accept(self) -> Union[Guild, User, GroupChannel]: async def accept(self) -> Union[Guild, User, GroupChannel]:

9
discord/iterators.py

@ -52,7 +52,6 @@ _Func = Callable[[T], Union[OT, Awaitable[OT]]]
OLDEST_OBJECT = Object(id=0) OLDEST_OBJECT = Object(id=0)
def _is_fake(item: Union[Messageable, Message]) -> bool: # I hate this too, but <circular imports> and performance exist def _is_fake(item: Union[Messageable, Message]) -> bool: # I hate this too, but <circular imports> and performance exist
try: try:
item.guild # type: ignore item.guild # type: ignore
@ -127,11 +126,11 @@ class CommandIterator:
} }
if self.applications: if self.applications:
kwargs['applications'] = True # Only sent if it's True... kwargs['applications'] = True # Only sent if it's True...
if (app := self.application): if app := self.application:
kwargs['application'] = app.id kwargs['application'] = app.id
if (query := self.query) is not None: if (query := self.query) is not None:
kwargs['query'] = query kwargs['query'] = query
if (cmds := self.command_ids): if cmds := self.command_ids:
kwargs['command_ids'] = cmds kwargs['command_ids'] = cmds
self.kwargs = kwargs self.kwargs = kwargs
@ -173,7 +172,9 @@ class CommandIterator:
for _ in range(3): for _ in range(3):
await state.ws.request_commands(**kwargs, limit=retrieve, nonce=nonce) await state.ws.request_commands(**kwargs, limit=retrieve, nonce=nonce)
try: try:
data: Optional[Dict[str, Any]] = await asyncio.wait_for(state.ws.wait_for('guild_application_commands_update', predicate), timeout=3) data: Optional[Dict[str, Any]] = await asyncio.wait_for(
state.ws.wait_for('guild_application_commands_update', predicate), timeout=3
)
except asyncio.TimeoutError: except asyncio.TimeoutError:
pass pass

5
discord/modal.py

@ -62,6 +62,7 @@ class Modal:
application: :class:`InteractionApplication` application: :class:`InteractionApplication`
The application that sent the modal. The application that sent the modal.
""" """
__slots__ = ('_state', 'interaction', 'id', 'nonce', 'title', 'custom_id', 'components', 'application') __slots__ = ('_state', 'interaction', 'id', 'nonce', 'title', 'custom_id', 'components', 'application')
def __init__(self, *, data: dict, interaction: Interaction): def __init__(self, *, data: dict, interaction: Interaction):
@ -110,7 +111,9 @@ class Modal:
state._interaction_cache[nonce] = (int(type), None, interaction.channel) state._interaction_cache[nonce] = (int(type), None, interaction.channel)
try: try:
await state.http.interact(type, self.to_dict(), interaction.channel, nonce=nonce, application_id=self.application.id) await state.http.interact(
type, self.to_dict(), interaction.channel, nonce=nonce, application_id=self.application.id
)
i = await state.client.wait_for( i = await state.client.wait_for(
'interaction_finish', 'interaction_finish',
check=lambda d: d.nonce == nonce, check=lambda d: d.nonce == nonce,

6
discord/profile.py

@ -99,7 +99,9 @@ class Profile:
self.premium_since: Optional[datetime] = utils.parse_time(data['premium_since']) self.premium_since: Optional[datetime] = utils.parse_time(data['premium_since'])
self.boosting_since: Optional[datetime] = utils.parse_time(data['premium_guild_since']) self.boosting_since: Optional[datetime] = utils.parse_time(data['premium_guild_since'])
self.connections: List[PartialConnection] = [PartialConnection(d) for d in data['connected_accounts']] # TODO: parse these self.connections: List[PartialConnection] = [
PartialConnection(d) for d in data['connected_accounts']
]
self.mutual_guilds: Optional[List[Guild]] = self._parse_mutual_guilds(data.get('mutual_guilds')) self.mutual_guilds: Optional[List[Guild]] = self._parse_mutual_guilds(data.get('mutual_guilds'))
self.mutual_friends: Optional[List[User]] = self._parse_mutual_friends(data.get('mutual_friends')) self.mutual_friends: Optional[List[User]] = self._parse_mutual_friends(data.get('mutual_friends'))
@ -151,12 +153,14 @@ class Profile:
class UserProfile(Profile, User): class UserProfile(Profile, User):
"""Represents a Discord user's profile. This is a :class:`User` with extended attributes.""" """Represents a Discord user's profile. This is a :class:`User` with extended attributes."""
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<UserProfile id={self.id} name={self.name!r} discriminator={self.discriminator!r} bot={self.bot} system={self.system} premium={self.premium}>' return f'<UserProfile id={self.id} name={self.name!r} discriminator={self.discriminator!r} bot={self.bot} system={self.system} premium={self.premium}>'
class MemberProfile(Profile, Member): class MemberProfile(Profile, Member):
"""Represents a Discord member's profile. This is a :class:`Member` with extended attributes.""" """Represents a Discord member's profile. This is a :class:`Member` with extended attributes."""
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f'<MemberProfile id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}' f'<MemberProfile id={self._user.id} name={self._user.name!r} discriminator={self._user.discriminator!r}'

24
discord/settings.py

@ -28,7 +28,16 @@ from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, TYPE_CHECKING from typing import Any, Dict, List, Optional, TYPE_CHECKING
from .activity import create_settings_activity from .activity import create_settings_activity
from .enums import FriendFlags, Locale, NotificationLevel, Status, StickerAnimationOptions, Theme, UserContentFilter, try_enum from .enums import (
FriendFlags,
Locale,
NotificationLevel,
Status,
StickerAnimationOptions,
Theme,
UserContentFilter,
try_enum,
)
from .guild_folder import GuildFolder from .guild_folder import GuildFolder
from .utils import MISSING, parse_time, utcnow from .utils import MISSING, parse_time, utcnow
@ -385,7 +394,8 @@ class ChannelSettings:
guild = self._state._get_guild(self._guild_id) guild = self._state._get_guild(self._guild_id)
return guild and guild.get_channel(self._channel_id) return guild and guild.get_channel(self._channel_id)
async def edit(self, async def edit(
self,
*, *,
muted: bool = MISSING, muted: bool = MISSING,
duration: Optional[int] = MISSING, duration: Optional[int] = MISSING,
@ -433,7 +443,7 @@ class ChannelSettings:
if duration is not None: if duration is not None:
mute_config = { mute_config = {
'selected_time_window': duration * 3600, 'selected_time_window': duration * 3600,
'end_time': (datetime.utcnow() + timedelta(hours=duration)).isoformat() 'end_time': (datetime.utcnow() + timedelta(hours=duration)).isoformat(),
} }
payload['mute_config'] = mute_config payload['mute_config'] = mute_config
@ -448,11 +458,7 @@ class ChannelSettings:
data = await self._state.http.edit_guild_settings(self._guild_id, fields) data = await self._state.http.edit_guild_settings(self._guild_id, fields)
if data: if data:
return ChannelSettings( return ChannelSettings(self._guild_id, data=data['channel_overrides'][str(self._channel_id)], state=self._state)
self._guild_id,
data=data['channel_overrides'][str(self._channel_id)],
state=self._state
)
else: else:
return self return self
@ -558,7 +564,7 @@ class GuildSettings:
if duration is not None: if duration is not None:
mute_config = { mute_config = {
'selected_time_window': duration * 3600, 'selected_time_window': duration * 3600,
'end_time': (datetime.utcnow() + timedelta(hours=duration)).isoformat() 'end_time': (datetime.utcnow() + timedelta(hours=duration)).isoformat(),
} }
payload['mute_config'] = mute_config payload['mute_config'] = mute_config

58
discord/state.py

@ -244,7 +244,11 @@ class MemberSidebar:
guild = self.guild guild = self.guild
ret = set() ret = set()
channels = [channel for channel in self.guild.channels if channel.type != ChannelType.stage_voice and channel.permissions_for(guild.me).read_messages] channels = [
channel
for channel in self.guild.channels
if channel.type != ChannelType.stage_voice and channel.permissions_for(guild.me).read_messages
]
if guild.rules_channel is not None: if guild.rules_channel is not None:
channels.insert(0, guild.rules_channel) channels.insert(0, guild.rules_channel)
@ -693,7 +697,11 @@ class ConnectionState:
self._private_channels_by_user.pop(recipient.id, None) self._private_channels_by_user.pop(recipient.id, None)
def _get_message(self, msg_id: Optional[int]) -> Optional[Message]: def _get_message(self, msg_id: Optional[int]) -> Optional[Message]:
return utils.find(lambda m: m.id == msg_id, reversed(self._messages)) if self._messages else utils.find(lambda m: m.id == msg_id, reversed(self._call_message_cache.values())) return (
utils.find(lambda m: m.id == msg_id, reversed(self._messages))
if self._messages
else utils.find(lambda m: m.id == msg_id, reversed(self._call_message_cache.values()))
)
def _add_guild_from_data(self, data: GuildPayload, *, from_ready: bool = False) -> Optional[Guild]: def _add_guild_from_data(self, data: GuildPayload, *, from_ready: bool = False) -> Optional[Guild]:
guild_id = int(data['id']) guild_id = int(data['id'])
@ -809,7 +817,7 @@ class ConnectionState:
extra_data.get('guilds', []), extra_data.get('guilds', []),
extra_data.get('merged_members', []), extra_data.get('merged_members', []),
data.get('merged_members', []), data.get('merged_members', []),
extra_data['merged_presences'].get('guilds', []) extra_data['merged_presences'].get('guilds', []),
): ):
guild_data['settings'] = utils.find( # type: ignore - This key does not actually exist in the payload guild_data['settings'] = utils.find( # type: ignore - This key does not actually exist in the payload
lambda i: i['guild_id'] == guild_data['id'], lambda i: i['guild_id'] == guild_data['id'],
@ -1474,7 +1482,7 @@ class ConnectionState:
continue continue
member = Member(data=item['member'], guild=guild, state=self) member = Member(data=item['member'], guild=guild, state=self)
if (presence := item['member'].get('presence')): if presence := item['member'].get('presence'):
member._presence_update(presence, empty_tuple) # type: ignore member._presence_update(presence, empty_tuple) # type: ignore
members.append(member) members.append(member)
@ -1496,10 +1504,12 @@ class ConnectionState:
old_member = Member._copy(member) old_member = Member._copy(member)
dispatch = bool(member._update(mdata)) dispatch = bool(member._update(mdata))
if (presence := mdata.get('presence')): if presence := mdata.get('presence'):
member._presence_update(presence, empty_tuple) # type: ignore member._presence_update(presence, empty_tuple) # type: ignore
if should_parse and (old_member._client_status != member._client_status or old_member._activities != member._activities): if should_parse and (
old_member._client_status != member._client_status or old_member._activities != member._activities
):
self.dispatch('presence_update', old_member, member) self.dispatch('presence_update', old_member, member)
user_update = member._update_inner_user(user) user_update = member._update_inner_user(user)
@ -1512,7 +1522,7 @@ class ConnectionState:
disregard.append(member) disregard.append(member)
else: else:
member = Member(data=mdata, guild=guild, state=self) member = Member(data=mdata, guild=guild, state=self)
if (presence := mdata.get('presence')): if presence := mdata.get('presence'):
member._presence_update(presence, empty_tuple) # type: ignore member._presence_update(presence, empty_tuple) # type: ignore
to_add.append(member) to_add.append(member)
@ -1533,10 +1543,12 @@ class ConnectionState:
old_member = Member._copy(member) old_member = Member._copy(member)
dispatch = bool(member._update(mdata)) dispatch = bool(member._update(mdata))
if (presence := mdata.get('presence')): if presence := mdata.get('presence'):
member._presence_update(presence, empty_tuple) # type: ignore member._presence_update(presence, empty_tuple) # type: ignore
if should_parse and (old_member._client_status != member._client_status or old_member._activities != member._activities): if should_parse and (
old_member._client_status != member._client_status or old_member._activities != member._activities
):
self.dispatch('presence_update', old_member, member) self.dispatch('presence_update', old_member, member)
user_update = member._update_inner_user(user) user_update = member._update_inner_user(user)
@ -1547,7 +1559,7 @@ class ConnectionState:
self.dispatch('member_update', old_member, member) self.dispatch('member_update', old_member, member)
else: else:
member = Member(data=mdata, guild=guild, state=self) member = Member(data=mdata, guild=guild, state=self)
if (presence := mdata.get('presence')): if presence := mdata.get('presence'):
member._presence_update(presence, empty_tuple) # type: ignore member._presence_update(presence, empty_tuple) # type: ignore
guild._member_list.insert(opdata['index'], member) # Race condition? guild._member_list.insert(opdata['index'], member) # Race condition?
@ -1557,7 +1569,11 @@ class ConnectionState:
try: try:
item = guild._member_list.pop(index) item = guild._member_list.pop(index)
except IndexError: except IndexError:
_log.debug('GUILD_MEMBER_LIST_UPDATE type DELETE referencing an unknown member index %s in %s. Discarding.', index, guild.id) _log.debug(
'GUILD_MEMBER_LIST_UPDATE type DELETE referencing an unknown member index %s in %s. Discarding.',
index,
guild.id,
)
continue continue
if item is not None: if item is not None:
@ -1581,7 +1597,9 @@ class ConnectionState:
def parse_guild_application_command_counts_update(self, data) -> None: def parse_guild_application_command_counts_update(self, data) -> None:
guild = self._get_guild(int(data['guild_id'])) guild = self._get_guild(int(data['guild_id']))
if guild is None: if guild is None:
_log.debug('GUILD_APPLICATION_COMMAND_COUNTS_UPDATE referencing an unknown guild ID: %s. Discarding.', data['guild_id']) _log.debug(
'GUILD_APPLICATION_COMMAND_COUNTS_UPDATE referencing an unknown guild ID: %s. Discarding.', data['guild_id']
)
return return
guild.command_counts = CommandCounts(data.get(0, 0), data.get(1, 0), data.get(2, 0)) guild.command_counts = CommandCounts(data.get(0, 0), data.get(1, 0), data.get(2, 0))
@ -1661,17 +1679,19 @@ class ConnectionState:
cache: bool, cache: bool,
force_scraping: bool = False, force_scraping: bool = False,
channels: List[abcSnowflake] = MISSING, channels: List[abcSnowflake] = MISSING,
delay: Union[int, float] = MISSING delay: Union[int, float] = MISSING,
): ):
if not guild.me: if not guild.me:
await guild.query_members(user_ids=[self.self_id], cache=True) # type: ignore - self_id is always present here await guild.query_members(user_ids=[self.self_id], cache=True) # type: ignore - self_id is always present here
if not force_scraping and any({ if not force_scraping and any(
{
guild.me.guild_permissions.administrator, guild.me.guild_permissions.administrator,
guild.me.guild_permissions.kick_members, guild.me.guild_permissions.kick_members,
guild.me.guild_permissions.ban_members, guild.me.guild_permissions.ban_members,
guild.me.guild_permissions.manage_roles, guild.me.guild_permissions.manage_roles,
}): }
):
request = self._chunk_requests.get(guild.id) request = self._chunk_requests.get(guild.id)
if request is None: if request is None:
self._chunk_requests[guild.id] = request = ChunkRequest(guild.id, self.loop, self._get_guild, cache=cache) self._chunk_requests[guild.id] = request = ChunkRequest(guild.id, self.loop, self._get_guild, cache=cache)
@ -1681,7 +1701,9 @@ class ConnectionState:
request = self._scrape_requests.get(guild.id) request = self._scrape_requests.get(guild.id)
if request is None: if request is None:
self._scrape_requests[guild.id] = request = MemberSidebar(guild, channels, chunk=False, cache=cache, loop=self.loop, delay=delay) self._scrape_requests[guild.id] = request = MemberSidebar(
guild, channels, chunk=False, cache=cache, loop=self.loop, delay=delay
)
request.start() request.start()
if wait: if wait:
@ -1700,7 +1722,9 @@ class ConnectionState:
request = self._scrape_requests.get(guild.id) request = self._scrape_requests.get(guild.id)
if request is None: if request is None:
self._scrape_requests[guild.id] = request = MemberSidebar(guild, channels, chunk=True, cache=True, loop=self.loop, delay=0) self._scrape_requests[guild.id] = request = MemberSidebar(
guild, channels, chunk=True, cache=True, loop=self.loop, delay=0
)
request.start() request.start()
if wait: if wait:

54
discord/tracking.py

@ -69,7 +69,7 @@ class ContextProperties: # Thank you Discord-S.C.U.M
'Verify Email': 'eyJsb2NhdGlvbiI6IlZlcmlmeSBFbWFpbCJ9', 'Verify Email': 'eyJsb2NhdGlvbiI6IlZlcmlmeSBFbWFpbCJ9',
'New Group DM': 'eyJsb2NhdGlvbiI6Ik5ldyBHcm91cCBETSJ9', 'New Group DM': 'eyJsb2NhdGlvbiI6Ik5ldyBHcm91cCBETSJ9',
'Add Friends to DM': 'eyJsb2NhdGlvbiI6IkFkZCBGcmllbmRzIHRvIERNIn0=', 'Add Friends to DM': 'eyJsb2NhdGlvbiI6IkFkZCBGcmllbmRzIHRvIERNIn0=',
'None': 'e30=' 'None': 'e30=',
} }
try: try:
@ -83,93 +83,67 @@ class ContextProperties: # Thank you Discord-S.C.U.M
@classmethod @classmethod
def _from_friends_page(cls) -> ContextProperties: def _from_friends_page(cls) -> ContextProperties:
data = { data = {'location': 'Friends'}
'location': 'Friends'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_context_menu(cls) -> ContextProperties: def _from_context_menu(cls) -> ContextProperties:
data = { data = {'location': 'ContextMenu'}
'location': 'ContextMenu'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_user_profile(cls) -> ContextProperties: def _from_user_profile(cls) -> ContextProperties:
data = { data = {'location': 'User Profile'}
'location': 'User Profile'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_add_friend_page(cls) -> ContextProperties: def _from_add_friend_page(cls) -> ContextProperties:
data = { data = {'location': 'Add Friend'}
'location': 'Add Friend'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_guild_header_menu(cls) -> ContextProperties: def _from_guild_header_menu(cls) -> ContextProperties:
data = { data = {'location': 'Guild Header'}
'location': 'Guild Header'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_group_dm(cls) -> ContextProperties: def _from_group_dm(cls) -> ContextProperties:
data = { data = {'location': 'Group DM'}
'location': 'Group DM'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_new_group_dm(cls) -> ContextProperties: def _from_new_group_dm(cls) -> ContextProperties:
data = { data = {'location': 'New Group DM'}
'location': 'New Group DM'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_dm_channel(cls) -> ContextProperties: def _from_dm_channel(cls) -> ContextProperties:
data = { data = {'location': 'DM Channel'}
'location': 'DM Channel'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_add_to_dm(cls) -> ContextProperties: def _from_add_to_dm(cls) -> ContextProperties:
data = { data = {'location': 'Add Friends to DM'}
'location': 'Add Friends to DM'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_app(cls) -> ContextProperties: def _from_app(cls) -> ContextProperties:
data = { data = {'location': '/app'}
'location': '/app'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_login(cls) -> ContextProperties: def _from_login(cls) -> ContextProperties:
data = { data = {'location': 'Login'}
'location': 'Login'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_register(cls) -> ContextProperties: def _from_register(cls) -> ContextProperties:
data = { data = {'location': 'Register'}
'location': 'Register'
}
return cls(data) return cls(data)
@classmethod @classmethod
def _from_verification(cls) -> ContextProperties: def _from_verification(cls) -> ContextProperties:
data = { data = {'location': 'Verify Email'}
'location': 'Verify Email'
}
return cls(data) return cls(data)
@classmethod @classmethod

2
discord/types/appinfo.py

@ -50,6 +50,7 @@ class _AppInfoOptional(TypedDict, total=False):
hook: bool hook: bool
max_participants: int max_participants: int
class _PartialAppInfoOptional(TypedDict, total=False): class _PartialAppInfoOptional(TypedDict, total=False):
rpc_origins: List[str] rpc_origins: List[str]
cover_image: str cover_image: str
@ -63,6 +64,7 @@ class _PartialAppInfoOptional(TypedDict, total=False):
class PartialAppInfo(_PartialAppInfoOptional, BaseAppInfo): class PartialAppInfo(_PartialAppInfoOptional, BaseAppInfo):
pass pass
class AppInfo(PartialAppInfo, _AppInfoOptional): class AppInfo(PartialAppInfo, _AppInfoOptional):
owner: User owner: User
integration_public: bool integration_public: bool

22
discord/user.py

@ -29,7 +29,16 @@ from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
import discord.abc import discord.abc
from .asset import Asset from .asset import Asset
from .colour import Colour from .colour import Colour
from .enums import Locale, AppCommandType, DefaultAvatar, HypeSquadHouse, PremiumType, RelationshipAction, RelationshipType, try_enum from .enums import (
Locale,
AppCommandType,
DefaultAvatar,
HypeSquadHouse,
PremiumType,
RelationshipAction,
RelationshipType,
try_enum,
)
from .errors import ClientException, NotFound from .errors import ClientException, NotFound
from .flags import PublicUserFlags from .flags import PublicUserFlags
from .iterators import FakeCommandIterator from .iterators import FakeCommandIterator
@ -67,6 +76,7 @@ __all__ = (
class Note: class Note:
"""Represents a Discord note.""" """Represents a Discord note."""
__slots__ = ('_state', '_note', '_user_id', '_user') __slots__ = ('_state', '_note', '_user_id', '_user')
def __init__( def __init__(
@ -171,7 +181,7 @@ class Note:
return base + '>' return base + '>'
def __len__(self) -> int: def __len__(self) -> int:
if (note := self._note): if note := self._note:
return len(note) return len(note)
return 0 return 0
@ -973,7 +983,9 @@ class User(BaseUser, discord.abc.Connectable, discord.abc.Messageable):
HTTPException HTTPException
Blocking the user failed. Blocking the user failed.
""" """
await self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value, action=RelationshipAction.block) await self._state.http.add_relationship(
self.id, type=RelationshipType.blocked.value, action=RelationshipAction.block
)
async def unblock(self) -> None: async def unblock(self) -> None:
"""|coro| """|coro|
@ -1017,9 +1029,7 @@ class User(BaseUser, discord.abc.Connectable, discord.abc.Messageable):
""" """
await self._state.http.send_friend_request(self.name, self.discriminator) await self._state.http.send_friend_request(self.name, self.discriminator)
async def profile( async def profile(self, *, with_mutuals: bool = True, fetch_note: bool = True) -> UserProfile:
self, *, with_mutuals: bool = True, fetch_note: bool = True
) -> UserProfile:
"""|coro| """|coro|
Gets the user's profile. Gets the user's profile.

17
discord/utils.py

@ -1262,6 +1262,7 @@ class Browser: # Inspired from https://github.com/NoahCardoza/CaptchaHarvester
def get_mac_browser(pkg: str, binary: str) -> Optional[os.PathLike]: def get_mac_browser(pkg: str, binary: str) -> Optional[os.PathLike]:
import plistlib as plist import plistlib as plist
pfile: str = f'{os.environ["HOME"]}/Library/Preferences/{pkg}.plist' pfile: str = f'{os.environ["HOME"]}/Library/Preferences/{pkg}.plist'
if os.path.exists(pfile): if os.path.exists(pfile):
with open(pfile, 'rb') as f: with open(pfile, 'rb') as f:
@ -1271,6 +1272,7 @@ class Browser: # Inspired from https://github.com/NoahCardoza/CaptchaHarvester
def get_windows_browser(browser: str) -> Optional[str]: def get_windows_browser(browser: str) -> Optional[str]:
import winreg as reg import winreg as reg
reg_path: str = f'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\{browser}.exe' reg_path: str = f'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\{browser}.exe'
exe_path: Optional[str] = None exe_path: Optional[str] = None
for install_type in reg.HKEY_CURRENT_USER, reg.HKEY_LOCAL_MACHINE: for install_type in reg.HKEY_CURRENT_USER, reg.HKEY_LOCAL_MACHINE:
@ -1288,6 +1290,7 @@ class Browser: # Inspired from https://github.com/NoahCardoza/CaptchaHarvester
def get_linux_browser(browser: str) -> Optional[str]: def get_linux_browser(browser: str) -> Optional[str]:
from shutil import which as exists from shutil import which as exists
possibilities: List[str] = [browser + channel for channel in ('', '-beta', '-dev', '-developer', '-canary')] possibilities: List[str] = [browser + channel for channel in ('', '-beta', '-dev', '-developer', '-canary')]
for browser in possibilities: for browser in possibilities:
if exists(browser): if exists(browser):
@ -1311,7 +1314,7 @@ class Browser: # Inspired from https://github.com/NoahCardoza/CaptchaHarvester
'chromium': functools.partial(get_linux_browser, 'chromium'), 'chromium': functools.partial(get_linux_browser, 'chromium'),
'microsoft-edge': functools.partial(get_linux_browser, 'microsoft-edge'), 'microsoft-edge': functools.partial(get_linux_browser, 'microsoft-edge'),
'opera': functools.partial(get_linux_browser, 'opera'), 'opera': functools.partial(get_linux_browser, 'opera'),
} },
} }
def get_browser(self, browser: Optional[BrowserEnum] = None) -> Optional[str]: def get_browser(self, browser: Optional[BrowserEnum] = None) -> Optional[str]:
@ -1337,14 +1340,15 @@ class Browser: # Inspired from https://github.com/NoahCardoza/CaptchaHarvester
width: int = 400, width: int = 400,
height: int = 500, height: int = 500,
browser_args: List[str] = [], browser_args: List[str] = [],
extensions: Optional[str] = None extensions: Optional[str] = None,
) -> None: ) -> None:
browser_command: List[str] = [self.browser, *browser_args] browser_command: List[str] = [self.browser, *browser_args]
if extensions: if extensions:
browser_command.append(f'--load-extension={extensions}') browser_command.append(f'--load-extension={extensions}')
browser_command.extend(( browser_command.extend(
(
'--disable-default-apps', '--disable-default-apps',
'--no-default-browser-check', '--no-default-browser-check',
'--no-check-default-browser', '--no-check-default-browser',
@ -1356,8 +1360,9 @@ class Browser: # Inspired from https://github.com/NoahCardoza/CaptchaHarvester
f'--user-data-dir={os.path.join(tempfile.TemporaryDirectory().name, "Profiles")}', f'--user-data-dir={os.path.join(tempfile.TemporaryDirectory().name, "Profiles")}',
f'--host-rules=MAP {domain} {server[0]}:{server[1]}', f'--host-rules=MAP {domain} {server[0]}:{server[1]}',
f'--window-size={width},{height}', f'--window-size={width},{height}',
f'--app=https://{domain}' f'--app=https://{domain}',
)) )
)
self.proc = subprocess.Popen(browser_command, stdout=-1, stderr=-1) self.proc = subprocess.Popen(browser_command, stdout=-1, stderr=-1)
@ -1392,7 +1397,7 @@ async def _get_build_number(session: ClientSession) -> int: # Thank you Discord
build_request = await session.get(build_url, timeout=7) build_request = await session.get(build_url, timeout=7)
build_file = await build_request.text() build_file = await build_request.text()
build_index = build_file.find('buildNumber') + 24 build_index = build_file.find('buildNumber') + 24
return int(build_file[build_index:build_index + 6]) return int(build_file[build_index : build_index + 6])
except asyncio.TimeoutError: except asyncio.TimeoutError:
_log.critical('Could not fetch client build number. Falling back to hardcoded value...') _log.critical('Could not fetch client build number. Falling back to hardcoded value...')
return 117300 return 117300

22
discord/voice_client.py

@ -259,9 +259,7 @@ class Player:
"""Indicates if we're playing audio, but if we're paused.""" """Indicates if we're playing audio, but if we're paused."""
return self._player and self._player.is_paused() return self._player and self._player.is_paused()
def play( def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], Any] = None) -> None:
self, source: AudioSource, *, after: Callable[[Optional[Exception]], Any] = None
) -> None:
"""Plays an :class:`AudioSource`. """Plays an :class:`AudioSource`.
The finalizer, ``after`` is called after the source has been exhausted The finalizer, ``after`` is called after the source has been exhausted
@ -508,7 +506,11 @@ class VoiceClient(VoiceProtocol):
await self._state.client.change_voice_state(channel=channel) await self._state.client.change_voice_state(channel=channel)
async def voice_disconnect(self) -> None: async def voice_disconnect(self) -> None:
_log.info('The voice handshake is being terminated for channel ID %s (guild ID %s).', (await self.channel._get_channel()).id, getattr(self.guild, 'id', None)) _log.info(
'The voice handshake is being terminated for channel ID %s (guild ID %s).',
(await self.channel._get_channel()).id,
getattr(self.guild, 'id', None),
)
if self.guild: if self.guild:
await self.guild.change_voice_state(channel=None) await self.guild.change_voice_state(channel=None)
else: else:
@ -738,7 +740,7 @@ class VoiceClient(VoiceProtocol):
@staticmethod @staticmethod
def _strip_header(data) -> bytes: def _strip_header(data) -> bytes:
if data[0] == 0xbe and data[1] == 0xde and len(data) > 4: if data[0] == 0xBE and data[1] == 0xDE and len(data) > 4:
_, length = struct.unpack_from('>HH', data) _, length = struct.unpack_from('>HH', data)
offset = 4 + length * 4 offset = 4 + length * 4
data = data[offset:] data = data[offset:]
@ -801,7 +803,7 @@ class VoiceClient(VoiceProtocol):
return self._strip_header(box.decrypt(bytes(data), bytes(nonce))) return self._strip_header(box.decrypt(bytes(data), bytes(nonce)))
def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], Any]=None) -> None: def play(self, source: AudioSource, *, after: Callable[[Optional[Exception]], Any] = None) -> None:
"""Plays an :class:`AudioSource`. """Plays an :class:`AudioSource`.
The finalizer, ``after`` is called after the source has been exhausted The finalizer, ``after`` is called after the source has been exhausted
@ -857,10 +859,10 @@ class VoiceClient(VoiceProtocol):
@sink.setter @sink.setter
def sink(self, value): def sink(self, value):
self.listener.sink = value self.listener.sink = value
#if not isinstance(value, AudioSink): # if not isinstance(value, AudioSink):
#raise TypeError('Expected AudioSink not {value.__class__.__name__}') # raise TypeError('Expected AudioSink not {value.__class__.__name__}')
#if self._recorder is None: # if self._recorder is None:
#raise ValueError('Not listening') # raise ValueError('Not listening')
def send_audio_packet(self, data: bytes) -> None: def send_audio_packet(self, data: bytes) -> None:
"""Sends an audio packet composed of the data. """Sends an audio packet composed of the data.

4
discord/welcome_screen.py

@ -61,9 +61,7 @@ class WelcomeChannel:
The emoji shown under the description. The emoji shown under the description.
""" """
def __init__( def __init__(self, *, channel: Snowflake, description: str, emoji: Optional[Union[PartialEmoji, Emoji]] = None) -> None:
self, *, channel: Snowflake, description: str, emoji: Optional[Union[PartialEmoji, Emoji]] = None
) -> None:
self.channel = channel self.channel = channel
self.description = description self.description = description
self.emoji = emoji self.emoji = emoji

Loading…
Cancel
Save