Browse Source

Implement joining guilds from id & lurking, bug fixes, doc fixes

pull/10109/head
dolfies 3 years ago
parent
commit
388863e521
  1. 171
      discord/client.py
  2. 9
      discord/gateway.py
  3. 47
      discord/guild.py
  4. 147
      discord/http.py
  5. 4
      discord/invite.py
  6. 2
      discord/team.py
  7. 60
      discord/tracking.py
  8. 28
      discord/types/guild.py
  9. 14
      discord/user.py

171
discord/client.py

@ -232,8 +232,8 @@ class Client:
self._client_status: _ClientStatus = _ClientStatus()
self._client_activities: Dict[Optional[str], Tuple[ActivityTypes, ...]] = {
None: None,
'this': None,
None: tuple(),
'this': tuple(),
}
self._session_count = 1
@ -275,8 +275,8 @@ class Client:
activities = self.initial_activities
status = self.initial_status
if status is None:
status = getattr(state.settings, 'status', None)
self.loop.create_task(self.change_presence(activities=activities, status=status))
status = getattr(state.settings, 'status', None) or Status.online
self.loop.create_task(self.change_presence(activities=activities, status=status)) # type: ignore
@property
def latency(self) -> float:
@ -435,7 +435,11 @@ class Client:
if not self._sync_presences:
return
if old_settings._status == new_settings._status and old_settings._custom_status == new_settings._custom_status:
if (
old_settings is not None
and old_settings._status == new_settings._status
and old_settings._custom_status == new_settings._custom_status
):
return # Nothing changed
status = new_settings.status
@ -443,7 +447,7 @@ class Client:
if (activity := new_settings.custom_activity) is not None:
activities.append(activity)
await self.change_presence(status=status, activities=activities, edit_settings=False)
await self.change_presence(status=status, activities=activities, edit_settings=False) # type: ignore
# Hooks
@ -582,7 +586,7 @@ class Client:
except ReconnectWebSocket as e:
_log.info('Got a request to %s the websocket.', e.op)
self.dispatch('disconnect')
ws_params.update(sequence=self.ws.sequence, resume=e.resume, session=self.ws.session_id)
ws_params.update(sequence=self.ws.sequence, resume=e.resume, session=self.ws.session_id) # type: ignore - These are always present at this point
continue
except (
OSError,
@ -606,7 +610,7 @@ class Client:
# If we get connection reset by peer then try to RESUME
if isinstance(exc, OSError) and exc.errno in (54, 10054):
ws_params.update(sequence=self.ws.sequence, initial=False, resume=True, session=self.ws.session_id)
ws_params.update(sequence=self.ws.sequence, initial=False, resume=True, session=self.ws.session_id) # type: ignore - These are always present at this point
continue
# We should only get this when an unhandled close code happens,
@ -624,7 +628,7 @@ class Client:
# Always try to RESUME the connection
# If the connection is not RESUME-able then the gateway will invalidate the session
# This is apparently what the official Discord client does
ws_params.update(sequence=self.ws.sequence, resume=True, session=self.ws.session_id)
ws_params.update(sequence=self.ws.sequence, resume=True, session=self.ws.session_id) # type: ignore - These are always present at this point
async def close(self) -> None:
"""|coro|
@ -734,8 +738,7 @@ class Client:
if value is None:
self._connection._activities = []
elif isinstance(value, BaseActivity):
# ConnectionState._activities is typehinted as List[ActivityPayload], we're passing List[Dict[str, Any]]
self._connection._activities = [value.to_dict()] # type: ignore
self._connection._activities = [value.to_dict()]
else:
raise TypeError('activity must derive from BaseActivity')
@ -750,8 +753,7 @@ class Client:
if not values:
self._connection._activities = []
elif all(isinstance(value, BaseActivity) for value in values):
# ConnectionState._activities is typehinted as List[ActivityPayload], we're passing List[Dict[str, Any]]
self._connection._activities = [value.to_dict() for value in values] # type: ignore
self._connection._activities = [value.to_dict() for value in values]
else:
raise TypeError('activity must derive from BaseActivity')
@ -854,7 +856,7 @@ class Client:
than 128 characters. See :issue:`1738` for more information.
"""
state = self._connection
activities = tuple(create_activity(d, state) for d in self._client_activities[None])
activities = tuple(create_activity(d, state) for d in self._client_activities[None]) # type: ignore
if activities is None and not self.is_closed():
activities = getattr(state.settings, 'custom_activity', [])
activities = [activities] if activities else activities
@ -1363,7 +1365,7 @@ class Client:
payload: Dict[str, Any] = {'status': status}
payload['custom_activity'] = custom_activity
await self.user.edit_settings(**payload)
await self.user.edit_settings(**payload) # type: ignore - user is always present when logged in
status_str = str(status)
activities_tuple = tuple(a.to_dict() for a in activities)
@ -1433,7 +1435,7 @@ class Client:
Parameters
-----------
with_counts: :class:`bool`
Whether to return approximate :attr:`.Guild.member_count` and :attr:`.Guild.presence_count`.
Whether to fill :attr:`.Guild.approximate_member_count` and :attr:`.Guild.approximate_presence_count`.
Defaults to ``True``.
Raises
@ -1448,7 +1450,10 @@ class Client:
"""
state = self._connection
guilds = await state.http.get_guilds(with_counts)
return [Guild(data=data, state=state) for data in guilds]
guilds = [Guild(data=data, state=state) for data in guilds]
for guild in guilds:
guild._cs_joined = True
return guilds
async def fetch_template(self, code: Union[Template, str]) -> Template:
"""|coro|
@ -1517,6 +1522,28 @@ class Client:
The guild from the ID.
"""
data = await self.http.get_guild(guild_id, with_counts)
guild = Guild(data=data, state=self._connection)
guild._cs_joined = True
return guild
async def fetch_guild_preview(self, guild_id: int, /) -> Guild:
"""|coro|
Retrieves a public :class:`.Guild` preview from an ID.
Raises
------
NotFound
Guild with given ID does not exist/is not public.
HTTPException
Retrieving the guild failed.
Returns
--------
:class:`.Guild`
The guild from the ID.
"""
data = await self.http.get_guild_preview(guild_id)
return Guild(data=data, state=self._connection)
async def create_guild(
@ -1570,7 +1597,69 @@ class Client:
data = await self.http.create_from_template(code, name, icon_base64)
else:
data = await self.http.create_guild(name, icon_base64)
return Guild(data=data, state=self._connection)
guild = Guild(data=data, state=self._connection)
guild._cs_joined = True
return guild
async def join_guild(self, guild_id: int, /, lurking: bool = False) -> Guild:
"""|coro|
Joins a discoverable :class:`.Guild`.
Parameters
-----------
guild_id: :class:`int`
The ID of the guild to join.
lurking: :class:`bool`
Whether to lurk the guild.
Raises
-------
NotFound
Guild with given ID does not exist/have discovery enabled.
HTTPException
Joining the guild failed.
Returns
--------
:class:`.Guild`
The guild that was joined.
"""
state = self._connection
data = await state.http.join_guild(guild_id, lurking, state.session_id)
guild = Guild(data=data, state=state)
guild._cs_joined = not lurking
return guild
async def leave_guild(self, guild: Snowflake, /, lurking: bool = MISSING) -> None:
"""|coro|
Leaves a guild. Equivalent to :meth:`Guild.leave`.
.. versionadded:: 2.0
Parameters
-----------
guild: :class:`abc.Snowflake`
The guild to leave.
lurking: :class:`bool`
Whether you are lurking the guild.
Raises
-------
HTTPException
Leaving the guild failed.
"""
lurking = lurking if lurking is not MISSING else MISSING
if lurking is MISSING:
attr = getattr(guild, 'joined', lurking)
if attr is not MISSING:
lurking = not attr
elif (new_guild := self._connection._get_guild(guild.id)) is not None:
lurking = not new_guild.joined
await self.http.leave_guild(guild.id, lurking=lurking)
async def fetch_stage_instance(self, channel_id: int, /) -> StageInstance:
"""|coro|
@ -1700,7 +1789,6 @@ class Client:
HTTPException
Revoking the invite failed.
"""
resolved = utils.resolve_invite(invite)
await self.http.delete_invite(resolved.code)
@ -1728,7 +1816,6 @@ class Client:
The guild joined. This is not the same guild that is
added to cache.
"""
if not isinstance(invite, Invite):
invite = await self.fetch_invite(invite, with_counts=False, with_expiration=False)
@ -1744,7 +1831,9 @@ class Client:
}
data = await state.http.accept_invite(invite.code, type, **kwargs)
if type is InviteType.guild:
return Guild(data=data['guild'], state=state)
guild = Guild(data=data['guild'], state=state)
guild._cs_joined = True
return guild
elif type is InviteType.group_dm:
return GroupChannel(data=data['channel'], state=state, me=state.user) # type: ignore
else:
@ -1973,7 +2062,7 @@ class Client:
The sticker you requested.
"""
data = await self.http.get_sticker(sticker_id)
cls, _ = _sticker_factory(data['type']) # type: ignore
cls, _ = _sticker_factory(data['type'])
return cls(state=self._connection, data=data) # type: ignore
async def fetch_sticker_packs(
@ -2014,6 +2103,8 @@ class Client:
Retrieves a sticker pack with the specified ID.
.. versionadded:: 2.0
Raises
-------
NotFound
@ -2108,7 +2199,7 @@ class Client:
"""
state = self._connection
channels = await state.http.get_private_channels()
return [_private_channel_factory(data['type'])[0](me=self.user, data=data, state=state) for data in channels]
return [_private_channel_factory(data['type'])[0](me=self.user, data=data, state=state) for data in channels] # type: ignore - user is always present when logged in
async def create_dm(self, user: Snowflake) -> DMChannel:
"""|coro|
@ -2161,10 +2252,10 @@ class Client:
:class:`.GroupChannel`
The new group channel.
"""
users = [str(u.id) for u in recipients]
users = [u.id for u in recipients]
state = self._connection
data = await state.http.start_group(users)
return GroupChannel(me=self.user, data=data, state=state)
return GroupChannel(me=self.user, data=data, state=state) # type: ignore - user is always present when logged in
@overload
async def send_friend_request(self, user: BaseUser) -> Relationship:
@ -2225,7 +2316,7 @@ class Client:
user = args[0]
if isinstance(user, BaseUser):
user = str(user)
username, discrim = user.split('#') # type: ignore
username, discrim = user.split('#')
elif len(args) == 2:
username, discrim = args # type: ignore
else:
@ -2278,6 +2369,10 @@ class Client:
Raises
-------
NotFound
The application was not found.
Forbidden
You do not own the application.
HTTPException
Retrieving the application failed.
@ -2295,6 +2390,20 @@ class Client:
Retrieves the partial application with the given ID.
.. versionadded:: 2.0
Parameters
-----------
app_id: :class:`int`
The ID of the partial application to fetch.
Raises
-------
NotFound
The partial application was not found.
HTTPException
Retrieving the partial application failed.
Returns
--------
:class:`.PartialApplication`
@ -2330,6 +2439,8 @@ class Client:
Retrieves the team with the given ID.
You must be a part of the team.
.. versionadded:: 2.0
Parameters
@ -2339,6 +2450,10 @@ class Client:
Raises
-------
NotFound
The team was not found.
Forbidden
You are not a part of the team.
HTTPException
Retrieving the team failed.
@ -2356,6 +2471,8 @@ class Client:
Creates an application.
.. versionadded:: 2.0
Parameters
----------
name: :class:`str`
@ -2380,6 +2497,8 @@ class Client:
Creates a team.
.. versionadded:: 2.0
Parameters
----------
name: :class:`str`

9
discord/gateway.py

@ -32,7 +32,7 @@ import threading
import traceback
import zlib
from typing import Any, Callable, Coroutine, Deque, Dict, List, TYPE_CHECKING, NamedTuple, Optional, TypeVar, Type
from typing import Any, Callable, Coroutine, Deque, Dict, List, TYPE_CHECKING, NamedTuple, Optional, TypeVar
import aiohttp
@ -56,6 +56,7 @@ if TYPE_CHECKING:
from typing_extensions import Self
from .client import Client
from .enums import Status
from .state import ConnectionState
from .types.snowflake import Snowflake
from .voice_client import VoiceClient
@ -654,7 +655,7 @@ class DiscordWebSocket:
self,
*,
activities: Optional[List[BaseActivity]] = None,
status: Optional[str] = None,
status: Optional[Status] = None,
since: float = 0.0,
afk: bool = False,
) -> None:
@ -670,7 +671,7 @@ class DiscordWebSocket:
payload = {
'op': self.PRESENCE,
'd': {'activities': activities_data, 'afk': afk, 'since': since, 'status': str(status)},
'd': {'activities': activities_data, 'afk': afk, 'since': since, 'status': str(status or 'online')},
}
sent = utils._to_json(payload)
@ -961,7 +962,7 @@ class DiscordVoiceWebSocket:
async def client_connect(self) -> None:
payload = {
'op': self.CLIENT_CONNECT,
'op': self.VIDEO,
'd': {
'audio_ssrc': self._connection.ssrc,
},

47
discord/guild.py

@ -105,10 +105,9 @@ _log = logging.getLogger(__name__)
if TYPE_CHECKING:
from .abc import Snowflake, SnowflakeTime
from .types.guild import (
Ban as BanPayload,
Guild as GuildPayload,
GuildPreview as GuildPreviewPayload,
RolePositionUpdate as RolePositionUpdatePayload,
GuildFeature,
)
from .types.threads import (
Thread as ThreadPayload,
@ -266,6 +265,12 @@ class Guild(Hashable):
The notification settings for the guild.
.. versionadded:: 2.0
keywords: Optional[:class:`str`]
Discovery search keywords for the guild.
.. versionadded:: 2.0
primary_category_id: Optional[:class:`int`]
The ID of the primary discovery category for the guild.
"""
__slots__ = (
@ -320,6 +325,12 @@ class Guild(Hashable):
'_true_online_count',
'_chunked',
'_member_list',
'keywords',
'primary_category_id',
'application_command_count',
'_load_id',
'_joined_at',
'_cs_joined',
)
_PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = {
@ -330,7 +341,7 @@ class Guild(Hashable):
3: _GuildLimit(emoji=250, stickers=60, bitrate=384e3, filesize=104857600),
}
def __init__(self, *, data: GuildPayload, state: ConnectionState) -> None:
def __init__(self, *, data: Union[GuildPayload, GuildPreviewPayload], state: ConnectionState) -> None:
self._chunked = False
self._roles: Dict[int, Role] = {}
self._channels: Dict[int, GuildChannel] = {}
@ -344,6 +355,7 @@ class Guild(Hashable):
self.notification_settings: Optional[GuildSettings] = None
self.command_counts: Optional[CommandCounts] = None
self._member_count: int = 0
self._presence_count: Optional[int] = None
self._from_data(data)
def _add_channel(self, channel: GuildChannel, /) -> None:
@ -438,9 +450,9 @@ class Guild(Hashable):
return role
def _from_data(self, guild: GuildPayload) -> None:
def _from_data(self, guild: Union[GuildPayload, GuildPreviewPayload]) -> None:
try:
self._member_count: int = guild['member_count']
self._member_count: int = guild['member_count'] # type: ignore - Handled below
except KeyError:
pass
@ -483,7 +495,8 @@ class Guild(Hashable):
self.stickers: Tuple[GuildSticker, ...] = tuple(
map(lambda d: state.store_sticker(self, d), guild.get('stickers', []))
)
self.features: List[GuildFeature] = guild.get('features', [])
self.features: List[str] = guild.get('features', [])
self.keywords: List[str] = guild.get('keywords', [])
self._icon: Optional[str] = guild.get('icon')
self._banner: Optional[str] = guild.get('banner')
self._splash: Optional[str] = guild.get('splash')
@ -506,10 +519,12 @@ class Guild(Hashable):
self.mfa_level: MFALevel = try_enum(MFALevel, guild.get('mfa_level', 0))
self.approximate_presence_count: Optional[int] = guild.get('approximate_presence_count')
self.approximate_member_count: Optional[int] = guild.get('approximate_member_count')
self._presence_count: Optional[int] = guild.get('approximate_presence_count')
self.owner_id: Optional[int] = utils._get_as_snowflake(guild, 'owner_id')
self.owner_application_id: Optional[int] = utils._get_as_snowflake(guild, 'application_id')
self.premium_progress_bar_enabled: bool = guild.get('premium_progress_bar_enabled', False)
self.application_command_count: int = guild.get('application_command_count', 0)
self.primary_category_id: Optional[int] = guild.get('primary_category_id')
self._joined_at = guild.get('joined_at')
large = None if self._member_count is 0 else self._member_count >= 250
self._large: Optional[bool] = guild.get('large', large)
@ -593,9 +608,23 @@ class Guild(Hashable):
""":class:`Member`: Similar to :attr:`Client.user` except an instance of :class:`Member`.
This is essentially used to get the member version of yourself.
"""
self_id = self._state.user.id # type: ignore - state.user won't be None if we're logged in
self_id = self._state.self_id
return self.get_member(self_id) # type: ignore - The self member is *always* cached
@utils.cached_slot_property('_cs_joined')
def joined(self) -> bool:
""":class:`bool`: Returns whether you are a member of this guild.
May not be accurate for :class:`Guild`s fetched over HTTP.
"""
if self.me or self.joined_at:
return True
return self._state.is_guild_evicted(self)
@property
def joined_at(self) -> Optional[datetime]:
""":class:`datetime.datetime`: Returns when you joined the guild."""
return utils.parse_time(self._joined_at)
@property
def voice_client(self) -> Optional[VoiceProtocol]:
"""Optional[:class:`VoiceProtocol`]: Returns the :class:`VoiceProtocol` associated with this guild, if any."""
@ -1519,7 +1548,7 @@ class Guild(Hashable):
HTTPException
Leaving the guild failed.
"""
await self._state.http.leave_guild(self.id)
await self._state.http.leave_guild(self.id, lurking=not self.joined)
async def delete(self) -> None:
"""|coro|

147
discord/http.py

@ -1197,6 +1197,33 @@ class HTTPClient:
return self.request(Route('GET', '/users/@me/guilds'), params=params, super_properties_to_track=True)
def join_guild(
self,
guild_id: Snowflake,
lurker: bool,
session_id: Optional[str] = MISSING,
load_id: str = MISSING,
location: str = MISSING,
) -> Response[guild.Guild]:
params = {
'lurker': str(lurker).lower(),
}
if lurker:
params['session_id'] = session_id or utils._generate_session_id()
if load_id is not MISSING:
params['recommendation_load_id'] = load_id
params['location'] = 'Guild%20Discovery'
if location is not MISSING:
params['location'] = location
props = ContextProperties._empty() if lurker else ContextProperties._from_lurking()
return self.request(
Route('PUT', '/guilds/{guild_id}/members/@me', guild_id=guild_id),
context_properties=props,
params=params,
json={},
)
def leave_guild(self, guild_id: Snowflake, lurking: bool = False) -> Response[None]:
r = Route('DELETE', '/users/@me/guilds/{guild_id}', guild_id=guild_id)
payload = {'lurking': lurking}
@ -2079,64 +2106,7 @@ class HTTPClient:
return self.request(Route('PATCH', '/users/@me/relationships/{user_id}', user_id=user_id), json=payload)
# Misc
async def get_gateway(self, *, encoding: str = 'json', zlib: bool = True) -> str:
# The gateway URL hasn't changed for over 5 years
# And, the official clients aren't GETting it anymore, sooooo...
self.zlib = zlib
if zlib:
value = 'wss://gateway.discord.gg?encoding={0}&v=9&compress=zlib-stream'
else:
value = 'wss://gateway.discord.gg?encoding={0}&v=9'
return value.format(encoding)
def get_user(self, user_id: Snowflake) -> Response[user.User]:
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
params: Dict[str, Any] = {'with_mutual_guilds': str(with_mutual_guilds).lower()}
if guild_id is not MISSING:
params['guild_id'] = guild_id
return self.request(Route('GET', '/users/{user_id}/profile', user_id=user_id), params=params)
def get_mutual_friends(self, user_id: Snowflake): # TODO: return type
return self.request(Route('GET', '/users/{user_id}/relationships', user_id=user_id))
def get_notes(self): # TODO: return type
return self.request(Route('GET', '/users/@me/notes'))
def get_note(self, user_id: Snowflake): # TODO: return type
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]:
payload = {'note': note or ''}
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]:
payload = {'house_id': house_id}
return self.request(Route('POST', '/hypesquad/online'), json=payload)
def leave_hypesquad_house(self) -> Response[None]:
return self.request(Route('DELETE', '/hypesquad/online'))
def get_settings(self): # TODO: return type
return self.request(Route('GET', '/users/@me/settings'))
def edit_settings(self, **payload): # TODO: return type, is this cheating?
return self.request(Route('PATCH', '/users/@me/settings'), json=payload)
def get_tracking(self): # TODO: return type
return self.request(Route('GET', '/users/@me/consent'))
def edit_tracking(self, payload):
return self.request(Route('POST', '/users/@me/consent'), json=payload)
# Connections
def get_connections(self):
return self.request(Route('GET', '/users/@me/connections'))
@ -2150,6 +2120,8 @@ class HTTPClient:
def get_connection_token(self, type: str, id: str):
return self.request(Route('GET', '/users/@me/connections/{type}/{id}/access-token', type=type, id=id))
# Applications
def get_my_applications(self, *, with_team_applications: bool = True) -> Response[List[appinfo.AppInfo]]:
params = {'with_team_applications': str(with_team_applications).lower()}
@ -2245,6 +2217,65 @@ class HTTPClient:
def reset_token(self, app_id: Snowflake):
return self.request(Route('POST', '/applications/{app_id}/bot/reset', app_id=app_id), super_properties_to_track=True)
# Misc
async def get_gateway(self, *, encoding: str = 'json', zlib: bool = True) -> str:
# The gateway URL hasn't changed for over 5 years
# And, the official clients aren't GETting it anymore, sooooo...
self.zlib = zlib
if zlib:
value = 'wss://gateway.discord.gg?encoding={0}&v=9&compress=zlib-stream'
else:
value = 'wss://gateway.discord.gg?encoding={0}&v=9'
return value.format(encoding)
def get_user(self, user_id: Snowflake) -> Response[user.User]:
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
params: Dict[str, Any] = {'with_mutual_guilds': str(with_mutual_guilds).lower()}
if guild_id is not MISSING:
params['guild_id'] = guild_id
return self.request(Route('GET', '/users/{user_id}/profile', user_id=user_id), params=params)
def get_mutual_friends(self, user_id: Snowflake): # TODO: return type
return self.request(Route('GET', '/users/{user_id}/relationships', user_id=user_id))
def get_notes(self): # TODO: return type
return self.request(Route('GET', '/users/@me/notes'))
def get_note(self, user_id: Snowflake): # TODO: return type
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]:
payload = {'note': note or ''}
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]:
payload = {'house_id': house_id}
return self.request(Route('POST', '/hypesquad/online'), json=payload)
def leave_hypesquad_house(self) -> Response[None]:
return self.request(Route('DELETE', '/hypesquad/online'))
def get_settings(self): # TODO: return type
return self.request(Route('GET', '/users/@me/settings'))
def edit_settings(self, **payload): # TODO: return type, is this cheating?
return self.request(Route('PATCH', '/users/@me/settings'), json=payload)
def get_tracking(self): # TODO: return type
return self.request(Route('GET', '/users/@me/consent'))
def edit_tracking(self, payload):
return self.request(Route('POST', '/users/@me/consent'), json=payload)
def mobile_report( # Report v1
self, guild_id: Snowflake, channel_id: Snowflake, message_id: Snowflake, reason: str
): # TODO: return type

4
discord/invite.py

@ -604,7 +604,9 @@ class Invite(Hashable):
if type is InviteType.guild:
from .guild import Guild
return Guild(data=data['guild'], state=state)
guild = Guild(data=data['guild'], state=state)
guild._cs_joined = True
return guild
elif type is InviteType.group_dm:
from .channel import GroupChannel

2
discord/team.py

@ -228,7 +228,7 @@ class Team:
user = args[0]
if isinstance(user, BaseUser):
user = str(user)
username, discrim = user.split('#') # type: ignore
username, discrim = user.split('#')
elif len(args) == 2:
username, discrim = args # type: ignore
else:

60
discord/tracking.py

@ -26,12 +26,15 @@ from __future__ import annotations
from base64 import b64encode
import json
from random import choice
from typing import Any, Dict, overload, Optional, TYPE_CHECKING
from typing import Dict, overload, Optional, TYPE_CHECKING
from .utils import MISSING
if TYPE_CHECKING:
from typing_extensions import Self
from .enums import ChannelType
from .types.snowflake import Snowflake
from .state import ConnectionState
@ -56,6 +59,8 @@ class ContextProperties: # Thank you Discord-S.C.U.M
def _encode_data(self, data) -> str:
library = {
'None': 'e30=',
# Locations
'Friends': 'eyJsb2NhdGlvbiI6IkZyaWVuZHMifQ==',
'ContextMenu': 'eyJsb2NhdGlvbiI6IkNvbnRleHRNZW51In0=',
'User Profile': 'eyJsb2NhdGlvbiI6IlVzZXIgUHJvZmlsZSJ9',
@ -69,80 +74,82 @@ class ContextProperties: # Thank you Discord-S.C.U.M
'Verify Email': 'eyJsb2NhdGlvbiI6IlZlcmlmeSBFbWFpbCJ9',
'New Group DM': 'eyJsb2NhdGlvbiI6Ik5ldyBHcm91cCBETSJ9',
'Add Friends to DM': 'eyJsb2NhdGlvbiI6IkFkZCBGcmllbmRzIHRvIERNIn0=',
'None': 'e30=',
# Sources
'Chat Input Blocker - Lurker Mode': 'eyJzb3VyY2UiOiJDaGF0IElucHV0IEJsb2NrZXIgLSBMdXJrZXIgTW9kZSJ9',
'Notice - Lurker Mode': 'eyJzb3VyY2UiOiJOb3RpY2UgLSBMdXJrZXIgTW9kZSJ9',
}
try:
return library[data.get('location', 'None')]
return library[self.target or 'None']
except KeyError:
return b64encode(json.dumps(data, separators=(',', ':')).encode()).decode('utf-8')
@classmethod
def _empty(cls) -> ContextProperties:
def _empty(cls) -> Self:
return cls({})
@classmethod
def _from_friends_page(cls) -> ContextProperties:
def _from_friends_page(cls) -> Self:
data = {'location': 'Friends'}
return cls(data)
@classmethod
def _from_context_menu(cls) -> ContextProperties:
def _from_context_menu(cls) -> Self:
data = {'location': 'ContextMenu'}
return cls(data)
@classmethod
def _from_user_profile(cls) -> ContextProperties:
def _from_user_profile(cls) -> Self:
data = {'location': 'User Profile'}
return cls(data)
@classmethod
def _from_add_friend_page(cls) -> ContextProperties:
def _from_add_friend_page(cls) -> Self:
data = {'location': 'Add Friend'}
return cls(data)
@classmethod
def _from_guild_header_menu(cls) -> ContextProperties:
def _from_guild_header_menu(cls) -> Self:
data = {'location': 'Guild Header'}
return cls(data)
@classmethod
def _from_group_dm(cls) -> ContextProperties:
def _from_group_dm(cls) -> Self:
data = {'location': 'Group DM'}
return cls(data)
@classmethod
def _from_new_group_dm(cls) -> ContextProperties:
def _from_new_group_dm(cls) -> Self:
data = {'location': 'New Group DM'}
return cls(data)
@classmethod
def _from_dm_channel(cls) -> ContextProperties:
def _from_dm_channel(cls) -> Self:
data = {'location': 'DM Channel'}
return cls(data)
@classmethod
def _from_add_to_dm(cls) -> ContextProperties:
def _from_add_to_dm(cls) -> Self:
data = {'location': 'Add Friends to DM'}
return cls(data)
@classmethod
def _from_app(cls) -> ContextProperties:
def _from_app(cls) -> Self:
data = {'location': '/app'}
return cls(data)
@classmethod
def _from_login(cls) -> ContextProperties:
def _from_login(cls) -> Self:
data = {'location': 'Login'}
return cls(data)
@classmethod
def _from_register(cls) -> ContextProperties:
def _from_register(cls) -> Self:
data = {'location': 'Register'}
return cls(data)
@classmethod
def _from_verification(cls) -> ContextProperties:
def _from_verification(cls) -> Self:
data = {'location': 'Verify Email'}
return cls(data)
@ -153,7 +160,7 @@ class ContextProperties: # Thank you Discord-S.C.U.M
guild_id: Snowflake = MISSING,
channel_id: Snowflake = MISSING,
channel_type: ChannelType = MISSING,
) -> ContextProperties:
) -> Self:
data: Dict[str, Snowflake] = {
'location': 'Accept Invite Page',
}
@ -172,7 +179,7 @@ class ContextProperties: # Thank you Discord-S.C.U.M
guild_id: Snowflake = MISSING,
channel_id: Snowflake = MISSING,
channel_type: ChannelType = MISSING,
) -> ContextProperties:
) -> Self:
data: Dict[str, Snowflake] = {
'location': 'Join Guild',
}
@ -192,7 +199,7 @@ class ContextProperties: # Thank you Discord-S.C.U.M
channel_id: Snowflake,
message_id: Snowflake,
channel_type: Optional[ChannelType],
) -> ContextProperties:
) -> Self:
data = {
'location': 'Invite Button Embed',
'location_guild_id': str(guild_id) if guild_id else None,
@ -202,9 +209,14 @@ class ContextProperties: # Thank you Discord-S.C.U.M
}
return cls(data)
@classmethod
def _from_lurking(cls, source: str = MISSING) -> Self:
data = {'source': source or choice(('Chat Input Blocker - Lurker Mode', 'Notice - Lurker Mode'))}
return cls(data)
@property
def location(self) -> Optional[str]:
return self._data.get('location') # type: ignore
def target(self) -> Optional[str]:
return self._data.get('location', data.get('source')) # type: ignore
@property
def guild_id(self) -> Optional[int]:
@ -232,10 +244,10 @@ class ContextProperties: # Thank you Discord-S.C.U.M
return self.value is not None
def __str__(self) -> str:
return self._data.get('location', 'None') # type: ignore
return self.target or 'None'
def __repr__(self) -> str:
return f'<ContextProperties location={self.location}>'
return f'<ContextProperties target={self.target!r}>'
def __eq__(self, other) -> bool:
return isinstance(other, ContextProperties) and self.value == other.value

28
discord/types/guild.py

@ -55,7 +55,6 @@ class UnavailableGuild(_UnavailableGuildOptional):
class _GuildOptional(TypedDict, total=False):
icon_hash: Optional[str]
owner: bool
permissions: str
widget_enabled: bool
widget_channel_id: Optional[Snowflake]
joined_at: Optional[str]
@ -78,31 +77,6 @@ MFALevel = Literal[0, 1]
VerificationLevel = Literal[0, 1, 2, 3, 4]
NSFWLevel = Literal[0, 1, 2, 3]
PremiumTier = Literal[0, 1, 2, 3]
GuildFeature = Literal[
'ANIMATED_ICON',
'BANNER',
'COMMERCE',
'COMMUNITY',
'DISCOVERABLE',
'FEATURABLE',
'INVITE_SPLASH',
'MEMBER_VERIFICATION_GATE_ENABLED',
'MONETIZATION_ENABLED',
'MORE_EMOJI',
'MORE_STICKERS',
'NEWS',
'PARTNERED',
'PREVIEW_ENABLED',
'PRIVATE_THREADS',
'ROLE_ICONS',
'SEVEN_DAY_THREAD_ARCHIVE',
'THREE_DAY_THREAD_ARCHIVE',
'TICKETED_EVENTS_ENABLED',
'VANITY_URL',
'VERIFIED',
'VIP_REGIONS',
'WELCOME_SCREEN_ENABLED',
]
class _BaseGuildPreview(UnavailableGuild):
@ -112,7 +86,7 @@ class _BaseGuildPreview(UnavailableGuild):
discovery_splash: Optional[str]
emojis: List[Emoji]
stickers: List[GuildSticker]
features: List[GuildFeature]
features: List[str]
description: Optional[str]

14
discord/user.py

@ -485,8 +485,6 @@ class ClientUser(BaseUser):
The IETF language tag used to identify the language the user is using.
mfa_enabled: :class:`bool`
Specifies if the user has MFA turned on and working.
premium: :class:`bool`
Specifies if the user is a premium user (i.e. has Discord Nitro).
premium_type: Optional[:class:`PremiumType`]
Specifies the type of premium a user has (i.e. Nitro or Nitro Classic). Could be None if the user is not premium.
note: :class:`Note`
@ -507,7 +505,6 @@ class ClientUser(BaseUser):
'phone',
'premium_type',
'note',
'premium',
'bio',
'nsfw_allowed',
)
@ -519,7 +516,6 @@ class ClientUser(BaseUser):
locale: Locale
_flags: int
mfa_enabled: bool
premium: bool
premium_type: Optional[PremiumType]
bio: Optional[str]
nsfw_allowed: bool
@ -542,8 +538,9 @@ class ClientUser(BaseUser):
self.locale = try_enum(Locale, data.get('locale', 'en-US'))
self._flags = data.get('flags', 0)
self.mfa_enabled = data.get('mfa_enabled', False)
self.premium = data.get('premium', False)
self.premium_type = try_enum(PremiumType, data.get('premium_type', None))
self.premium_type = try_enum(PremiumType, data['premium_type']) if 'premium_type' in data else None
self.bio = data.get('bio')
self.nsfw_allowed = data.get('nsfw_allowed', False)
self.bio = data.get('bio') or None
self.nsfw_allowed = data.get('nsfw_allowed', False)
@ -562,6 +559,11 @@ class ClientUser(BaseUser):
"""
return self._state._relationships.get(user_id)
@property
def premium(self) -> bool:
"""Indicates if the user is a premium user (i.e. has Discord Nitro)."""
return self.premium_type is not None
@property
def relationships(self) -> List[Relationship]:
"""List[:class:`User`]: Returns all the relationships that the user has."""

Loading…
Cancel
Save