You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
584 lines
22 KiB
584 lines
22 KiB
"""
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2021-present Dolfies
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
DEALINGS IN THE SOFTWARE.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
|
|
from .activity import create_settings_activity
|
|
from .enums import FriendFlags, NotificationLevel, Status, StickerAnimationOptions, Theme, UserContentFilter, try_enum
|
|
from .guild_folder import GuildFolder
|
|
from .utils import copy_doc, MISSING, parse_time, utcnow
|
|
|
|
if TYPE_CHECKING:
|
|
from .abc import GuildChannel
|
|
from .activity import CustomActivity
|
|
from .guild import Guild
|
|
from .state import ConnectionState
|
|
from .tracking import Tracking
|
|
|
|
__all__ = (
|
|
'ChannelSettings',
|
|
'GuildSettings',
|
|
'UserSettings',
|
|
)
|
|
|
|
|
|
class UserSettings:
|
|
"""Represents the Discord client settings.
|
|
|
|
Attributes
|
|
----------
|
|
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.
|
|
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.
|
|
gif_auto_play: :class:`bool`
|
|
Whether or not to automatically play gifs that are in the chat.
|
|
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.
|
|
show_current_game: :class:`bool`
|
|
Whether or not to display the game that you are currently playing.
|
|
stream_notifications_enabled: :class:`bool`
|
|
Unknown.
|
|
timezone_offset: :class:`int`
|
|
The timezone offset to use.
|
|
view_nsfw_guilds: :class:`bool`
|
|
Whether or not to show NSFW guilds on iOS.
|
|
"""
|
|
|
|
if TYPE_CHECKING: # Fuck me
|
|
afk_timeout: int
|
|
allow_accessibility_detection: bool
|
|
animate_emojis: bool
|
|
contact_sync_enabled: bool
|
|
convert_emoticons: bool
|
|
default_guilds_restricted: bool
|
|
detect_platform_accounts: bool
|
|
developer_mode: bool
|
|
disable_games_tab: bool
|
|
enable_tts_command: bool
|
|
gif_auto_play: bool
|
|
inline_attachment_media: bool
|
|
inline_embed_media: bool
|
|
locale: str
|
|
message_display_compact: bool
|
|
native_phone_integration_enabled: bool
|
|
render_embeds: bool
|
|
render_reactions: bool
|
|
show_current_game: bool
|
|
stream_notifications_enabled: bool
|
|
timezone_offset: int
|
|
view_nsfw_guilds: bool
|
|
|
|
def __init__(self, *, data, state: ConnectionState) -> None:
|
|
self._state = state
|
|
self._update(data)
|
|
|
|
def __repr__(self) -> str:
|
|
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:
|
|
RAW_VALUES = {
|
|
'afk_timeout',
|
|
'allow_accessibility_detection',
|
|
'animate_emojis',
|
|
'contact_sync_enabled',
|
|
'convert_emoticons',
|
|
'default_guilds_restricted',
|
|
'detect_platform_accounts',
|
|
'developer_mode',
|
|
'disable_games_tab',
|
|
'enable_tts_command',
|
|
'gif_auto_play',
|
|
'inline_attachment_media',
|
|
'inline_embed_media',
|
|
'locale',
|
|
'message_display_compact',
|
|
'native_phone_integration_enabled',
|
|
'render_embeds',
|
|
'render_reactions',
|
|
'show_current_game',
|
|
'stream_notifications_enabled',
|
|
'timezone_offset',
|
|
'view_nsfw_guilds',
|
|
}
|
|
|
|
for key, value in data.items():
|
|
if key in RAW_VALUES:
|
|
setattr(self, key, value)
|
|
else:
|
|
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
|
|
def tracking(self) -> Optional[Tracking]:
|
|
"""Optional[:class:`Tracking`]: Returns your tracking settings if available."""
|
|
return self._state.consents
|
|
|
|
@property
|
|
def animate_stickers(self) -> StickerAnimationOptions:
|
|
""":class:`StickerAnimationOptions`: Whether or not to animate stickers in the chat."""
|
|
return try_enum(StickerAnimationOptions, getattr(self, '_animate_stickers', 0))
|
|
|
|
@property
|
|
def custom_activity(self) -> Optional[CustomActivity]:
|
|
"""Optional[:class:`CustomActivity]: The custom activity you have set."""
|
|
return create_settings_activity(data=getattr(self, '_custom_status', None), state=self._state)
|
|
|
|
@property
|
|
def explicit_content_filter(self) -> UserContentFilter:
|
|
""":class:`UserContentFilter`: The filter for explicit content in all messages."""
|
|
return try_enum(UserContentFilter, getattr(self, '_explicit_content_filter', 1))
|
|
|
|
@property
|
|
def friend_source_flags(self) -> FriendFlags:
|
|
""":class:`FriendFlags`: Who can add you as a friend."""
|
|
return FriendFlags._from_dict(getattr(self, '_friend_source_flags', {'all': True}))
|
|
|
|
@property
|
|
def guild_folders(self) -> List[GuildFolder]:
|
|
"""List[:class:`GuildFolder`]: A list of guild folders."""
|
|
state = self._state
|
|
return [GuildFolder(data=folder, state=state) for folder in getattr(self, '_guild_folders', [])]
|
|
|
|
@property
|
|
def guild_positions(self) -> List[Guild]:
|
|
"""List[:class:`Guild`]: A list of guilds in order of the guild/guild icons that are on the left hand side of the UI."""
|
|
return list(filter(None, map(self._get_guild, getattr(self, '_guild_positions', []))))
|
|
|
|
@property
|
|
def passwordless(self) -> bool:
|
|
""":class:`bool`: Whether the account is passwordless."""
|
|
return getattr(self, '_passwordless', False)
|
|
|
|
@property
|
|
def restricted_guilds(self) -> List[Guild]:
|
|
"""List[:class:`Guild`]: A list of guilds that you will not receive DMs from."""
|
|
return list(filter(None, map(self._get_guild, getattr(self, '_restricted_guilds', []))))
|
|
|
|
@property
|
|
def status(self) -> Status:
|
|
return try_enum(Status, getattr(self, '_status', 'online'))
|
|
|
|
@property
|
|
def theme(self) -> Theme:
|
|
""":class:`Theme`: The theme of the Discord UI."""
|
|
return try_enum(Theme, getattr(self, '_theme', 'dark')) # Sane default :)
|
|
|
|
|
|
class MuteConfig:
|
|
def __init__(self, muted: bool, config: Dict[str, str]) -> None:
|
|
until = parse_time(config.get('end_time'))
|
|
if until is not None:
|
|
if until <= utcnow():
|
|
muted = False
|
|
until = None
|
|
|
|
self.muted: bool = muted
|
|
self.until: Optional[datetime] = until
|
|
|
|
for item in {'__bool__', '__eq__', '__float__', '__int__', '__str__'}:
|
|
setattr(self, item, getattr(muted, item))
|
|
|
|
def __repr__(self) -> str:
|
|
return f'<MuteConfig muted={self.muted} until={self.until}>'
|
|
|
|
def __bool__(self) -> bool:
|
|
return bool(self.muted)
|
|
|
|
def __eq__(self, other) -> bool:
|
|
return self.muted == other
|
|
|
|
def __ne__(self, other) -> bool:
|
|
return not self.muted == other
|
|
|
|
|
|
class ChannelSettings:
|
|
"""Represents a channel's notification settings"""
|
|
|
|
if TYPE_CHECKING:
|
|
_channel_id: int
|
|
level: NotificationLevel
|
|
muted: MuteConfig
|
|
collapsed: bool
|
|
|
|
def __init__(self, guild_id, *, data: Dict[str, Any] = {}, state: ConnectionState) -> None:
|
|
self._guild_id: int = guild_id
|
|
self._state = state
|
|
self._update(data)
|
|
|
|
def _update(self, data: Dict[str, Any]) -> None:
|
|
self._channel_id = int(data['channel_id'])
|
|
self.collapsed = data.get('collapsed', False)
|
|
|
|
self.level = try_enum(NotificationLevel, data.get('message_notifications', 3)) # type: ignore
|
|
self.muted = MuteConfig(data.get('muted', False), data.get('mute_config') or {})
|
|
|
|
@property
|
|
def channel(self) -> Optional[GuildChannel]:
|
|
"""Optional[:class:`GuildChannel`]: Returns the channel these settings are for."""
|
|
guild = self._state._get_guild(self._guild_id)
|
|
return guild and guild.get_channel(self._channel_id)
|
|
|
|
async def edit(self,
|
|
*,
|
|
muted: bool = MISSING,
|
|
duration: Optional[int] = MISSING,
|
|
collapsed: bool = MISSING,
|
|
level: NotificationLevel = MISSING,
|
|
) -> Optional[ChannelSettings]:
|
|
"""|coro|
|
|
|
|
Edits the channel's notification settings.
|
|
|
|
All parameters are optional.
|
|
|
|
Parameters
|
|
-----------
|
|
muted: :class:`bool`
|
|
Indicates if the channel should be muted or not.
|
|
duration: Optional[Union[:class:`int`, :class:`float`]]
|
|
The amount of time in hours that the channel should be muted for.
|
|
Defaults to indefinite.
|
|
collapsed: :class:`bool`
|
|
Unknown.
|
|
level: :class:`NotificationLevel`
|
|
Determines what level of notifications you receive for the channel.
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Editing the settings failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`ChannelSettings`
|
|
The new notification settings.
|
|
"""
|
|
payload = {}
|
|
data = None
|
|
|
|
if muted is not MISSING:
|
|
payload['muted'] = muted
|
|
|
|
if duration is not MISSING:
|
|
if muted is MISSING:
|
|
payload['muted'] = True
|
|
|
|
if duration is not None:
|
|
mute_config = {
|
|
'selected_time_window': duration * 3600,
|
|
'end_time': (datetime.utcnow() + timedelta(hours=duration)).isoformat()
|
|
}
|
|
payload['mute_config'] = mute_config
|
|
|
|
if collapsed is not MISSING:
|
|
payload['collapsed'] = collapsed
|
|
|
|
if level is not MISSING:
|
|
payload['message_notifications'] = level.value
|
|
|
|
if payload:
|
|
fields = {'channel_overrides': {str(self._channel_id): payload}}
|
|
data = await self._state.http.edit_guild_settings(self._guild_id, fields)
|
|
|
|
if data:
|
|
return ChannelSettings(
|
|
self._guild_id,
|
|
data=data['channel_overrides'][str(self._channel_id)],
|
|
state=self._state
|
|
)
|
|
else:
|
|
return self
|
|
|
|
|
|
class GuildSettings:
|
|
"""Represents a guild's notification settings."""
|
|
|
|
if TYPE_CHECKING:
|
|
_channel_overrides: Dict[int, ChannelSettings]
|
|
_guild_id: int
|
|
version: int
|
|
muted: MuteConfig
|
|
suppress_everyone: bool
|
|
suppress_roles: bool
|
|
hide_muted_channels: bool
|
|
mobile_push_notifications: bool
|
|
level: NotificationLevel
|
|
|
|
def __init__(self, *, data: Dict[str, Any], state: ConnectionState) -> None:
|
|
self._state = state
|
|
self._update(data)
|
|
|
|
def _update(self, data: Dict[str, Any]) -> None:
|
|
self._guild_id = guild_id = int(data['guild_id'])
|
|
self.version = data.get('version', -1) # Overriden by real data
|
|
self.suppress_everyone = data.get('suppress_everyone', False)
|
|
self.suppress_roles = data.get('suppress_roles', False)
|
|
self.hide_muted_channels = data.get('hide_muted_channels', False)
|
|
self.mobile_push_notifications = data.get('mobile_push', True)
|
|
|
|
self.level = try_enum(NotificationLevel, data.get('message_notifications', 3))
|
|
self.muted = MuteConfig(data.get('muted', False), data.get('mute_config') or {})
|
|
self._channel_overrides = overrides = {}
|
|
state = self._state
|
|
for override in data.get('channel_overrides', []):
|
|
channel_id = int(override['channel_id'])
|
|
overrides[channel_id] = ChannelSettings(guild_id, data=override, state=state)
|
|
|
|
@property
|
|
def guild(self) -> Optional[Guild]:
|
|
"""Optional[:class:`Guild`]: Returns the guild that these settings are for."""
|
|
return self._state._get_guild(self._guild_id)
|
|
|
|
@property
|
|
def channel_overrides(self) -> List[ChannelSettings]:
|
|
"""List[:class:`ChannelSettings`: Returns a list of all the overrided channel notification settings."""
|
|
return list(self._channel_overrides.values())
|
|
|
|
async def edit(
|
|
self,
|
|
muted: bool = MISSING,
|
|
duration: Optional[int] = MISSING,
|
|
level: NotificationLevel = MISSING,
|
|
suppress_everyone: bool = MISSING,
|
|
suppress_roles: bool = MISSING,
|
|
mobile_push_notifications: bool = MISSING,
|
|
hide_muted_channels: bool = MISSING,
|
|
) -> Optional[GuildSettings]:
|
|
"""|coro|
|
|
|
|
Edits the guild's notification settings.
|
|
|
|
All parameters are optional.
|
|
|
|
Parameters
|
|
-----------
|
|
muted: :class:`bool`
|
|
Indicates if the guild should be muted or not.
|
|
duration: Optional[Union[:class:`int`, :class:`float`]]
|
|
The amount of time in hours that the guild should be muted for.
|
|
Defaults to indefinite.
|
|
level: :class:`NotificationLevel`
|
|
Determines what level of notifications you receive for the guild.
|
|
suppress_everyone: :class:`bool`
|
|
Indicates if @everyone mentions should be suppressed for the guild.
|
|
suppress_roles: :class:`bool`
|
|
Indicates if role mentions should be suppressed for the guild.
|
|
mobile_push_notifications: :class:`bool`
|
|
Indicates if push notifications should be sent to mobile devices for this guild.
|
|
hide_muted_channels: :class:`bool`
|
|
Indicates if channels that are muted should be hidden from the sidebar.
|
|
|
|
Raises
|
|
-------
|
|
HTTPException
|
|
Editing the settings failed.
|
|
|
|
Returns
|
|
--------
|
|
:class:`GuildSettings`
|
|
The new notification settings.
|
|
"""
|
|
payload = {}
|
|
data = None
|
|
|
|
if muted is not MISSING:
|
|
payload['muted'] = muted
|
|
|
|
if duration is not MISSING:
|
|
if muted is MISSING:
|
|
payload['muted'] = True
|
|
|
|
if duration is not None:
|
|
mute_config = {
|
|
'selected_time_window': duration * 3600,
|
|
'end_time': (datetime.utcnow() + timedelta(hours=duration)).isoformat()
|
|
}
|
|
payload['mute_config'] = mute_config
|
|
|
|
if level is not MISSING:
|
|
payload['message_notifications'] = level.value
|
|
|
|
if suppress_everyone is not MISSING:
|
|
payload['suppress_everyone'] = suppress_everyone
|
|
|
|
if suppress_roles is not MISSING:
|
|
payload['suppress_roles'] = suppress_roles
|
|
|
|
if mobile_push_notifications is not MISSING:
|
|
payload['mobile_push'] = mobile_push_notifications
|
|
|
|
if hide_muted_channels is not MISSING:
|
|
payload['hide_muted_channels'] = hide_muted_channels
|
|
|
|
if payload:
|
|
data = await self._state.http.edit_guild_settings(self._guild_id, payload)
|
|
|
|
if data:
|
|
return GuildSettings(data=data, state=self._state)
|
|
else:
|
|
return self
|
|
|