Browse Source

Fix tracking settings, add settings.edit

pull/10109/head
dolfies 4 years ago
parent
commit
54c9c3588c
  1. 116
      discord/settings.py
  2. 121
      discord/tracking.py
  3. 118
      discord/user.py

116
discord/settings.py

@ -27,10 +27,10 @@ from __future__ import annotations
from datetime import datetime, timedelta 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_activity from .activity import create_settings_activity
from .enums import FriendFlags, NotificationLevel, Status, StickerAnimationOptions, Theme, UserContentFilter, try_enum from .enums import FriendFlags, 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 copy_doc, MISSING, parse_time, utcnow
if TYPE_CHECKING: if TYPE_CHECKING:
from .abc import GuildChannel from .abc import GuildChannel
@ -134,6 +134,9 @@ class UserSettings:
def __repr__(self) -> str: def __repr__(self) -> str:
return '<Settings>' return '<Settings>'
def _get_guild(self, id: int) -> Optional[Guild]:
return self._state._get_guild(int(id))
def _update(self, data: Dict[str, Any]) -> None: def _update(self, data: Dict[str, Any]) -> None:
RAW_VALUES = { RAW_VALUES = {
'afk_timeout', 'afk_timeout',
@ -166,6 +169,110 @@ class UserSettings:
else: else:
setattr(self, '_' + key, value) setattr(self, '_' + key, value)
async def edit(self, **kwargs) -> UserSettings:
"""|coro|
Edits the client user's settings.
.. versionchanged:: 2.0
The edit is no longer in-place, instead the newly edited settings are returned.
Parameters
----------
afk_timeout: :class:`int`
How long (in seconds) the user needs to be AFK until Discord
sends push notifications to your mobile device.
allow_accessibility_detection: :class:`bool`
Whether or not to allow Discord to track screen reader usage.
animate_emojis: :class:`bool`
Whether or not to animate emojis in the chat.
animate_stickers: :class:`StickerAnimationOptions`
Whether or not to animate stickers in the chat.
contact_sync_enabled: :class:`bool`
Whether or not to enable the contact sync on Discord mobile.
convert_emoticons: :class:`bool`
Whether or not to automatically convert emoticons into emojis.
e.g. :-) -> 😃
default_guilds_restricted: :class:`bool`
Whether or not to automatically disable DMs between you and
members of new guilds you join.
detect_platform_accounts: :class:`bool`
Whether or not to automatically detect accounts from services
like Steam and Blizzard when you open the Discord client.
developer_mode: :class:`bool`
Whether or not to enable developer mode.
disable_games_tab: :class:`bool`
Whether or not to disable the showing of the Games tab.
enable_tts_command: :class:`bool`
Whether or not to allow tts messages to be played/sent.
explicit_content_filter: :class:`UserContentFilter`
The filter for explicit content in all messages.
friend_source_flags: :class:`FriendFlags`
Who can add you as a friend.
gif_auto_play: :class:`bool`
Whether or not to automatically play gifs that are in the chat.
guild_positions: List[:class:`abc.Snowflake`]
A list of guilds in order of the guild/guild icons that are on
the left hand side of the UI.
inline_attachment_media: :class:`bool`
Whether or not to display attachments when they are uploaded in chat.
inline_embed_media: :class:`bool`
Whether or not to display videos and images from links posted in chat.
locale: :class:`str`
The :rfc:`3066` language identifier of the locale to use for the language
of the Discord client.
message_display_compact: :class:`bool`
Whether or not to use the compact Discord display mode.
native_phone_integration_enabled: :class:`bool`
Whether or not to enable the new Discord mobile phone number friend
requesting features.
render_embeds: :class:`bool`
Whether or not to render embeds that are sent in the chat.
render_reactions: :class:`bool`
Whether or not to render reactions that are added to messages.
restricted_guilds: List[:class:`abc.Snowflake`]
A list of guilds that you will not receive DMs from.
show_current_game: :class:`bool`
Whether or not to display the game that you are currently playing.
stream_notifications_enabled: :class:`bool`
Unknown.
theme: :class:`Theme`
The theme of the Discord UI.
timezone_offset: :class:`int`
The timezone offset to use.
view_nsfw_guilds: :class:`bool`
Whether or not to show NSFW guilds on iOS.
Raises
-------
HTTPException
Editing the settings failed.
Returns
-------
:class:`.UserSettings`
The client user's updated settings.
"""
return await self._state.user.edit_settings(**kwargs) # type: ignore
async def fetch_tracking(self) -> Tracking:
"""|coro|
Retrieves your :class:`Tracking` settings.
Raises
------
HTTPException
Retrieving the tracking settings failed.
Returns
-------
:class:`Tracking`
The tracking settings.
"""
data = await self._state.http.get_tracking()
return Tracking(state=self._state, data=data)
@property @property
def tracking(self) -> Optional[Tracking]: def tracking(self) -> Optional[Tracking]:
"""Optional[:class:`Tracking`]: Returns your tracking settings if available.""" """Optional[:class:`Tracking`]: Returns your tracking settings if available."""
@ -179,7 +286,7 @@ class UserSettings:
@property @property
def custom_activity(self) -> Optional[CustomActivity]: def custom_activity(self) -> Optional[CustomActivity]:
"""Optional[:class:`CustomActivity]: The custom activity you have set.""" """Optional[:class:`CustomActivity]: The custom activity you have set."""
return create_activity(getattr(self, '_custom_status', None)) return create_settings_activity(data=getattr(self, '_custom_status', None), state=self._state)
@property @property
def explicit_content_filter(self) -> UserContentFilter: def explicit_content_filter(self) -> UserContentFilter:
@ -221,9 +328,6 @@ class UserSettings:
""":class:`Theme`: The theme of the Discord UI.""" """:class:`Theme`: The theme of the Discord UI."""
return try_enum(Theme, getattr(self, '_theme', 'dark')) # Sane default :) return try_enum(Theme, getattr(self, '_theme', 'dark')) # Sane default :)
def _get_guild(self, id: int) -> Optional[Guild]:
return self._state._get_guild(int(id))
class MuteConfig: class MuteConfig:
def __init__(self, muted: bool, config: Dict[str, str]) -> None: def __init__(self, muted: bool, config: Dict[str, str]) -> None:

121
discord/tracking.py

@ -27,9 +27,14 @@ from __future__ import annotations
from base64 import b64encode from base64 import b64encode
import json import json
from typing import Any, Dict, Optional from typing import Any, Dict, overload, Optional, TYPE_CHECKING
from .types.snowflake import Snowflake from .utils import MISSING
if TYPE_CHECKING:
from .enums import ChannelType
from .types.snowflake import Snowflake
from .state import ConnectionState
__all__ = ( __all__ = (
'ContextProperties', 'ContextProperties',
@ -46,7 +51,7 @@ class ContextProperties: # Thank you Discord-S.C.U.M
__slots__ = ('_data', 'value') __slots__ = ('_data', 'value')
def __init__(self, data) -> None: def __init__(self, data) -> None:
self._data: Dict[str, any] = data self._data: Dict[str, Snowflake] = data
self.value: str = self._encode_data(data) self.value: str = self._encode_data(data)
def _encode_data(self, data) -> str: def _encode_data(self, data) -> str:
@ -139,13 +144,6 @@ class ContextProperties: # Thank you Discord-S.C.U.M
} }
return cls(data) return cls(data)
@classmethod
def _from_accept_invite_page_blank(cls) -> ContextProperties:
data = {
'location': 'Accept Invite Page'
}
return cls(data)
@classmethod @classmethod
def _from_app(cls) -> ContextProperties: def _from_app(cls) -> ContextProperties:
data = { data = {
@ -176,44 +174,63 @@ class ContextProperties: # Thank you Discord-S.C.U.M
@classmethod @classmethod
def _from_accept_invite_page( def _from_accept_invite_page(
cls, *, guild_id: Snowflake, channel_id: Snowflake, channel_type: int cls,
*,
guild_id: Snowflake = MISSING,
channel_id: Snowflake = MISSING,
channel_type: ChannelType = MISSING,
) -> ContextProperties: ) -> ContextProperties:
data = { data: Dict[str, Snowflake] = {
'location': 'Accept Invite Page', 'location': 'Accept Invite Page',
'location_guild_id': str(guild_id),
'location_channel_id': str(channel_id),
'location_channel_type': int(channel_type)
} }
if guild_id is not MISSING:
data['location_guild_id'] = str(guild_id)
if channel_id is not MISSING:
data['location_channel_id'] = str(channel_id)
if channel_type is not MISSING:
data['location_channel_type'] = int(channel_type)
return cls(data) return cls(data)
@classmethod @classmethod
def _from_join_guild_popup( def _from_join_guild_popup(
cls, *, guild_id: Snowflake, channel_id: Snowflake, channel_type: int cls,
*,
guild_id: Snowflake = MISSING,
channel_id: Snowflake = MISSING,
channel_type: ChannelType = MISSING,
) -> ContextProperties: ) -> ContextProperties:
data = { data: Dict[str, Snowflake] = {
'location': 'Join Guild', 'location': 'Join Guild',
'location_guild_id': str(guild_id),
'location_channel_id': str(channel_id),
'location_channel_type': int(channel_type)
} }
if guild_id is not MISSING:
data['location_guild_id'] = str(guild_id)
if channel_id is not MISSING:
data['location_channel_id'] = str(channel_id)
if channel_type is not MISSING:
data['location_channel_type'] = int(channel_type)
return cls(data) return cls(data)
@classmethod @classmethod
def _from_invite_embed( def _from_invite_embed(
cls, *, guild_id: Snowflake, channel_id: Snowflake, message_id: Snowflake, channel_type: int cls,
*,
guild_id: Optional[Snowflake],
channel_id: Snowflake,
message_id: Snowflake,
channel_type: Optional[ChannelType],
) -> ContextProperties: ) -> ContextProperties:
data = { data = {
'location': 'Invite Button Embed', 'location': 'Invite Button Embed',
'location_guild_id': str(guild_id), 'location_guild_id': str(guild_id) if guild_id else None,
'location_channel_id': str(channel_id), 'location_channel_id': str(channel_id),
'location_channel_type': int(channel_type), 'location_channel_type': int(channel_type) if channel_type else None,
'location_message_id': str(message_id) 'location_message_id': str(message_id),
} }
return cls(data) return cls(data)
@property @property
def location(self) -> Optional[str]: def location(self) -> Optional[str]:
return self._data.get('location') return self._data.get('location') # type: ignore
@property @property
def guild_id(self) -> Optional[int]: def guild_id(self) -> Optional[int]:
@ -229,9 +246,7 @@ class ContextProperties: # Thank you Discord-S.C.U.M
@property @property
def channel_type(self) -> Optional[int]: def channel_type(self) -> Optional[int]:
data = self._data.get('location_channel_type') return self._data.get('location_channel_type') # type: ignore
if data is not None:
return data
@property @property
def message_id(self) -> Optional[int]: def message_id(self) -> Optional[int]:
@ -243,10 +258,10 @@ class ContextProperties: # Thank you Discord-S.C.U.M
return self.value is not None return self.value is not None
def __str__(self) -> str: def __str__(self) -> str:
return self._data.get('location', 'None') return self._data.get('location', 'None') # type: ignore
def __repr__(self) -> str: def __repr__(self) -> str:
return '<ContextProperties location={0.location}>'.format(self) return f'<ContextProperties location={self.location}>'
def __eq__(self, other) -> bool: def __eq__(self, other) -> bool:
return isinstance(other, ContextProperties) and self.value == other.value return isinstance(other, ContextProperties) and self.value == other.value
@ -262,7 +277,51 @@ class Tracking:
---------- ----------
personalization: :class:`bool` personalization: :class:`bool`
Whether you have consented to your data being used for personalization. Whether you have consented to your data being used for personalization.
usage_statistics: :class:`bool`
Whether you have consented to your data being used for usage statistics.
""" """
def __init__(self, data: Dict[str, Any]): # TODO: rest of the values __slots__ = ('_state', 'personalization', 'usage_statistics')
def __init__(self, *, data: Dict[str, Dict[str, bool]], state: ConnectionState) -> None:
self._state = state
self._update(data)
def __bool__(self) -> bool:
return any({self.personalization, self.usage_statistics})
def _update(self, data: Dict[str, Dict[str, bool]]):
self.personalization = data.get('personalization', {}).get('consented', False) self.personalization = data.get('personalization', {}).get('consented', False)
self.usage_statistics = data.get('usage_statistics', {}).get('consented', False)
@overload
async def edit(self) -> None:
...
@overload
async def edit(
self,
*,
personalization: bool = ...,
usage_statistics: bool = ...,
) -> None:
...
async def edit(self, **kwargs) -> None:
"""|coro|
Edits your tracking settings.
Parameters
----------
personalization: :class:`bool`
Whether you have consented to your data being used for personalization.
usage_statistics: :class:`bool`
Whether you have consented to your data being used for usage statistics.
"""
payload = {
'grant': [k for k, v in kwargs.items() if v is True],
'revoke': [k for k, v in kwargs.items() if v is False],
}
data = await self._state.http.edit_tracking(payload)
self._update(data)

118
discord/user.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import Any, Dict, List, Optional, Type, TypeVar, TYPE_CHECKING, Union from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Union
import discord.abc import discord.abc
from .asset import Asset from .asset import Asset
@ -36,7 +36,7 @@ from .iterators import FakeCommandIterator
from .object import Object from .object import Object
from .relationship import Relationship from .relationship import Relationship
from .settings import UserSettings from .settings import UserSettings
from .utils import _bytes_to_base64_data, _get_as_snowflake, cached_slot_property, parse_time, snowflake_time, MISSING from .utils import _bytes_to_base64_data, _get_as_snowflake, cached_slot_property, copy_doc, snowflake_time, MISSING
if TYPE_CHECKING: if TYPE_CHECKING:
from datetime import datetime from datetime import datetime
@ -478,6 +478,10 @@ class ClientUser(BaseUser):
Specifies the type of premium a user has (i.e. Nitro or Nitro Classic). Could be None if the user is not premium. 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` note: :class:`Note`
The user's note. Not pre-fetched. The user's note. Not pre-fetched.
nsfw_allowed: :class:`bool`
Specifies if the user should be allowed to access NSFW content.
.. versionadded:: 2.0
""" """
__slots__ = ( __slots__ = (
@ -491,6 +495,7 @@ class ClientUser(BaseUser):
'note', 'note',
'premium', 'premium',
'bio', 'bio',
'nsfw_allowed',
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -503,6 +508,7 @@ class ClientUser(BaseUser):
premium: bool premium: bool
premium_type: Optional[PremiumType] premium_type: Optional[PremiumType]
bio: Optional[str] bio: Optional[str]
nsfw_allowed: bool
def __init__(self, *, state: ConnectionState, data: UserPayload) -> None: def __init__(self, *, state: ConnectionState, data: UserPayload) -> None:
super().__init__(state=state, data=data) super().__init__(state=state, data=data)
@ -525,11 +531,7 @@ class ClientUser(BaseUser):
self.premium = data.get('premium', 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.get('premium_type', None))
self.bio = data.get('bio') or None self.bio = data.get('bio') or None
self.nsfw_allowed = data.get('nsfw_allowed', False)
@property
def connected_accounts(self) -> Optional[List[dict]]:
"""Optional[List[:class:`dict`]]: Returns a list of all linked accounts for this user if available."""
return self._state._connected_accounts
def get_relationship(self, user_id: int) -> Relationship: def get_relationship(self, user_id: int) -> Relationship:
"""Retrieves the :class:`Relationship` if applicable. """Retrieves the :class:`Relationship` if applicable.
@ -581,7 +583,7 @@ class ClientUser(BaseUser):
accent_color: Colour = MISSING, accent_color: Colour = MISSING,
bio: Optional[str] = MISSING, bio: Optional[str] = MISSING,
date_of_birth: datetime = MISSING, date_of_birth: datetime = MISSING,
) -> ClientUser: ) -> ClientUser:
"""|coro| """|coro|
Edits the current profile of the client. Edits the current profile of the client.
@ -642,7 +644,7 @@ class ClientUser(BaseUser):
:class:`ClientUser` :class:`ClientUser`
The newly edited client user. The newly edited client user.
""" """
args: Dict[str, any] = {} args: Dict[str, Any] = {}
if any(x is not MISSING for x in ('new_password', 'email', 'username', 'discriminator')): if any(x is not MISSING for x in ('new_password', 'email', 'username', 'discriminator')):
if password is MISSING: if password is MISSING:
@ -700,7 +702,7 @@ class ClientUser(BaseUser):
else: else:
await http.change_hypesquad_house(house.value) await http.change_hypesquad_house(house.value)
data = await http.edit_profile(**args) data = await http.edit_profile(args)
try: try:
http._token(data['token']) http._token(data['token'])
except KeyError: except KeyError:
@ -730,90 +732,8 @@ class ClientUser(BaseUser):
data = await self._state.http.get_settings() data = await self._state.http.get_settings()
return UserSettings(data=data, state=self._state) return UserSettings(data=data, state=self._state)
@copy_doc(UserSettings.edit)
async def edit_settings(self, **kwargs) -> UserSettings: # TODO: I really wish I didn't have to do this... async def edit_settings(self, **kwargs) -> UserSettings: # TODO: I really wish I didn't have to do this...
"""|coro|
Edits the client user's settings.
.. versionchanged:: 2.0
The edit is no longer in-place, instead the newly edited settings are returned.
Parameters
----------
afk_timeout: :class:`int`
How long (in seconds) the user needs to be AFK until Discord
sends push notifications to your mobile device.
allow_accessibility_detection: :class:`bool`
Whether or not to allow Discord to track screen reader usage.
animate_emojis: :class:`bool`
Whether or not to animate emojis in the chat.
animate_stickers: :class:`StickerAnimationOptions`
Whether or not to animate stickers in the chat.
contact_sync_enabled: :class:`bool`
Whether or not to enable the contact sync on Discord mobile.
convert_emoticons: :class:`bool`
Whether or not to automatically convert emoticons into emojis.
e.g. :-) -> 😃
default_guilds_restricted: :class:`bool`
Whether or not to automatically disable DMs between you and
members of new guilds you join.
detect_platform_accounts: :class:`bool`
Whether or not to automatically detect accounts from services
like Steam and Blizzard when you open the Discord client.
developer_mode: :class:`bool`
Whether or not to enable developer mode.
disable_games_tab: :class:`bool`
Whether or not to disable the showing of the Games tab.
enable_tts_command: :class:`bool`
Whether or not to allow tts messages to be played/sent.
explicit_content_filter: :class:`UserContentFilter`
The filter for explicit content in all messages.
friend_source_flags: :class:`FriendFlags`
Who can add you as a friend.
gif_auto_play: :class:`bool`
Whether or not to automatically play gifs that are in the chat.
guild_positions: List[:class:`abc.Snowflake`]
A list of guilds in order of the guild/guild icons that are on
the left hand side of the UI.
inline_attachment_media: :class:`bool`
Whether or not to display attachments when they are uploaded in chat.
inline_embed_media: :class:`bool`
Whether or not to display videos and images from links posted in chat.
locale: :class:`str`
The :rfc:`3066` language identifier of the locale to use for the language
of the Discord client.
message_display_compact: :class:`bool`
Whether or not to use the compact Discord display mode.
native_phone_integration_enabled: :class:`bool`
Whether or not to enable the new Discord mobile phone number friend
requesting features.
render_embeds: :class:`bool`
Whether or not to render embeds that are sent in the chat.
render_reactions: :class:`bool`
Whether or not to render reactions that are added to messages.
restricted_guilds: List[:class:`abc.Snowflake`]
A list of guilds that you will not receive DMs from.
show_current_game: :class:`bool`
Whether or not to display the game that you are currently playing.
stream_notifications_enabled: :class:`bool`
Unknown.
theme: :class:`Theme`
The theme of the Discord UI.
timezone_offset: :class:`int`
The timezone offset to use.
view_nsfw_guilds: :class:`bool`
Whether or not to show NSFW guilds on iOS.
Raises
-------
HTTPException
Editing the settings failed.
Returns
-------
:class:`.UserSettings`
The client user's updated settings.
"""
payload = {} payload = {}
content_filter = kwargs.pop('explicit_content_filter', None) content_filter = kwargs.pop('explicit_content_filter', None)
@ -842,6 +762,10 @@ class ClientUser(BaseUser):
if status: if status:
payload['status'] = status.value payload['status'] = status.value
custom_activity = kwargs.pop('custom_activity', MISSING)
if custom_activity is not MISSING:
payload['custom_status'] = custom_activity and custom_activity.to_settings_dict()
theme = kwargs.pop('theme', None) theme = kwargs.pop('theme', None)
if theme: if theme:
payload['theme'] = theme.value payload['theme'] = theme.value
@ -895,7 +819,7 @@ class User(BaseUser, discord.abc.Connectable, discord.abc.Messageable):
self._stored: bool = False self._stored: bool = False
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<User id={self.id} name={self.name!r} discriminator={self.discriminator!r} bot={self.bot} system={self.system}>' return f'<{self.__class__.__name__} id={self.id} name={self.name!r} discriminator={self.discriminator!r} bot={self.bot} system={self.system}>'
def __del__(self) -> None: def __del__(self) -> None:
try: try:
@ -910,10 +834,10 @@ class User(BaseUser, discord.abc.Connectable, discord.abc.Messageable):
self._stored = False self._stored = False
return self return self
def _get_voice_client_key(self) -> Union[int, str]: def _get_voice_client_key(self) -> Tuple[int, str]:
return self._state.self_id, 'self_id' return self._state.self_id, 'self_id'
def _get_voice_state_pair(self) -> Union[int, int]: def _get_voice_state_pair(self) -> Tuple[int, int]:
return self._state.self_id, self.dm_channel.id return self._state.self_id, self.dm_channel.id
async def _get_channel(self) -> DMChannel: async def _get_channel(self) -> DMChannel:
@ -974,7 +898,7 @@ class User(BaseUser, discord.abc.Connectable, discord.abc.Messageable):
*, *,
limit: Optional[int] = None, limit: Optional[int] = None,
command_ids: Optional[List[int]] = [], command_ids: Optional[List[int]] = [],
**kwargs, **_,
): ):
"""Returns an iterator that allows you to see what user commands are available to use on this user. """Returns an iterator that allows you to see what user commands are available to use on this user.

Loading…
Cancel
Save