36
36
Dict ,
37
37
Any ,
38
38
Awaitable ,
39
+ Iterable ,
40
+ Iterator ,
39
41
NamedTuple ,
40
42
TYPE_CHECKING
41
43
)
42
44
from typing_extensions import Literal
43
45
44
- from discord .utils import _bytes_to_base64_data
45
-
46
46
if TYPE_CHECKING :
47
47
from os import PathLike
48
48
from .state import ConnectionState
93
93
from .automod import AutoModRule , AutoModTriggerMetadata , AutoModAction
94
94
from .application_commands import SlashCommand , MessageCommand , UserCommand , Localizations
95
95
96
+ MISSING = utils .MISSING
97
+
98
+ __all__ = (
99
+ 'GuildFeatures' ,
100
+ 'Guild' ,
101
+ )
102
+
96
103
97
104
class _GuildLimit (NamedTuple ):
98
105
emoji : int
@@ -101,9 +108,6 @@ class _GuildLimit(NamedTuple):
101
108
filesize : int
102
109
103
110
104
- MISSING = utils .MISSING
105
-
106
-
107
111
async def default_callback (interaction , * args , ** kwargs ):
108
112
await interaction .respond (
109
113
'This command has no callback set.'
@@ -112,6 +116,129 @@ async def default_callback(interaction, *args, **kwargs):
112
116
)
113
117
114
118
119
+ class GuildFeatures (Iterable [str ], dict ):
120
+ """
121
+ Represents a guild's features.
122
+
123
+ This class mainly exists to make it easier to edit a guild's features.
124
+
125
+ .. versionadded:: 2.0
126
+
127
+ .. container:: operations
128
+
129
+ .. describe:: 'FEATURE_NAME' in features
130
+
131
+ Checks if the guild has the feature.
132
+
133
+ .. describe:: features.FEATURE_NAME
134
+
135
+ Checks if the guild has the feature. Returns ``False`` if it doesn't.
136
+
137
+ .. describe:: features.FEATURE_NAME = True
138
+
139
+ Enables the feature in the features object, but does not enable it in the guild itself except if you pass it to :meth:`Guild.edit`.
140
+
141
+ .. describe:: features.FEATURE_NAME = False
142
+
143
+ Disables the feature in the features object, but does not disable it in the guild itself except if you pass it to :meth:`Guild.edit`.
144
+
145
+ .. describe:: del features.FEATURE_NAME
146
+
147
+ The same as ``features.FEATURE_NAME = False``
148
+
149
+ .. describe:: features.parsed()
150
+
151
+ Returns a list of all features that are/should be enabled.
152
+
153
+ .. describe:: features.merge(other)
154
+
155
+ Returns a new object with the features of both objects merged.
156
+ If a feature is missing in the other object, it will be ignored.
157
+
158
+ .. describe:: features == other
159
+
160
+ Checks if two feature objects are equal.
161
+
162
+ .. describe:: features != other
163
+
164
+ Checks if two feature objects are not equal.
165
+
166
+ .. describe:: iter(features)
167
+
168
+ Returns an iterator over the enabled features.
169
+ """
170
+ def __init__ (self , / , initial : List [str ], ** features : bool ):
171
+ """
172
+ Parameters
173
+ -----------
174
+ initial: :class:`list`
175
+ The initial features to set.
176
+ **features: :class:`bool`
177
+ The features to set. If the value is ``True`` then the feature is/will be enabled.
178
+ If the value is ``False`` then the feature will be disabled.
179
+ """
180
+ for feature in initial :
181
+ features [feature ] = True
182
+ self .__dict__ .update (features )
183
+
184
+ def __iter__ (self ) -> Iterator [str ]:
185
+ return [feature for feature , value in self .__dict__ .items () if value is True ].__iter__ ()
186
+
187
+ def __contains__ (self , item : str ) -> bool :
188
+ return item in self .__dict__ and self .__dict__ [item ] is True
189
+
190
+ def __getattr__ (self , item : str ) -> bool :
191
+ return self .__dict__ .get (item , False )
192
+
193
+ def __setattr__ (self , key : str , value : bool ) -> None :
194
+ self .__dict__ [key ] = value
195
+
196
+ def __delattr__ (self , item : str ) -> None :
197
+ self .__dict__ [item ] = False
198
+
199
+ def __repr__ (self ) -> str :
200
+ return f'<GuildFeatures { self .__dict__ !r} >'
201
+
202
+ def __str__ (self ) -> str :
203
+ return str (self .__dict__ )
204
+
205
+ def merge (self , other : GuildFeatures ) -> GuildFeatures :
206
+ base = copy .copy (self .__dict__ )
207
+ for key , value in other .items ():
208
+ base [key ] = value
209
+
210
+ return GuildFeatures (** base )
211
+
212
+ def parsed (self ) -> List [str ]:
213
+ return [name for name , value in self .__dict__ .items () if value is True ]
214
+
215
+ def __eq__ (self , other : GuildFeatures ) -> bool :
216
+ current = self .__dict__
217
+ other = other .__dict__
218
+
219
+ all_keys = set (current .keys ()) | set (other .keys ())
220
+
221
+ for key in all_keys :
222
+ try :
223
+ current_value = current [key ]
224
+ except KeyError :
225
+ if other [key ] is True :
226
+ return False
227
+ else :
228
+ try :
229
+ other_value = other [key ]
230
+ except KeyError :
231
+ pass
232
+ else :
233
+ if current_value != other_value :
234
+ return False
235
+
236
+ return True
237
+
238
+ def __ne__ (self , other : GuildFeatures ) -> bool :
239
+ return not self .__eq__ (other )
240
+
241
+
115
242
class Guild (Hashable ):
116
243
"""Represents a Discord guild.
117
244
@@ -1486,6 +1613,7 @@ async def edit(
1486
1613
self ,
1487
1614
name : str = MISSING ,
1488
1615
description : str = MISSING ,
1616
+ features : GuildFeatures = MISSING ,
1489
1617
icon : Optional [bytes ] = MISSING ,
1490
1618
banner : Optional [bytes ] = MISSING ,
1491
1619
splash : Optional [bytes ] = MISSING ,
@@ -1523,6 +1651,10 @@ async def edit(
1523
1651
description: :class:`str`
1524
1652
The new description of the guild. This is only available to guilds that
1525
1653
contain ``PUBLIC`` in :attr:`Guild.features`.
1654
+ features: :class:`GuildFeatures`
1655
+ Features to enable/disable will be merged in to the current features.
1656
+ See the `discord api documentation <https://discord.com/developers/docs/resources/guild#guild-object-mutable-guild-features>`_
1657
+ for a list of currently mutable features and the required permissions.
1526
1658
icon: :class:`bytes`
1527
1659
A :term:`py:bytes-like object` representing the icon. Only PNG/JPEG is supported.
1528
1660
GIF is only available to guilds that contain ``ANIMATED_ICON`` in :attr:`Guild.features`.
@@ -1605,6 +1737,10 @@ async def edit(
1605
1737
if splash is not MISSING :
1606
1738
fields ['splash' ] = utils ._bytes_to_base64_data (splash )
1607
1739
1740
+ if features is not MISSING :
1741
+ current_features = GuildFeatures (self .features )
1742
+ fields ['features' ] = current_features .merge (features )
1743
+
1608
1744
if discovery_splash is not MISSING :
1609
1745
fields ['discovery_splash' ] = utils ._bytes_to_base64_data (discovery_splash )
1610
1746
@@ -3154,7 +3290,7 @@ async def create_scheduled_event(
3154
3290
if cover_image :
3155
3291
if not isinstance (cover_image , bytes ):
3156
3292
raise ValueError (f'cover_image must be of type bytes, not { cover_image .__class__ .__name__ } ' )
3157
- as_base64 = _bytes_to_base64_data (cover_image )
3293
+ as_base64 = utils . _bytes_to_base64_data (cover_image )
3158
3294
fields ['image' ] = as_base64
3159
3295
3160
3296
data = await self ._state .http .create_guild_event (guild_id = self .id , fields = fields , reason = reason )
0 commit comments