This repository has been archived by the owner on Aug 1, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 81
/
channel.py
403 lines (330 loc) · 11.7 KB
/
channel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
import six
from holster.enum import Enum
from disco.util.snowflake import to_snowflake
from disco.util.functional import cached_property, one_or_many, chunks
from disco.types.user import User
from disco.types.base import SlottedModel, Field, AutoDictField, snowflake, enum, text
from disco.types.permissions import Permissions, Permissible, PermissionValue
from disco.voice.client import VoiceClient
ChannelType = Enum(
GUILD_TEXT=0,
DM=1,
GUILD_VOICE=2,
GROUP_DM=3,
)
PermissionOverwriteType = Enum(
ROLE='role',
MEMBER='member'
)
class ChannelSubType(SlottedModel):
channel_id = Field(None)
@cached_property
def channel(self):
return self.client.state.channels.get(self.channel_id)
class PermissionOverwrite(ChannelSubType):
"""
A PermissionOverwrite for a :class:`Channel`.
Attributes
----------
id : snowflake
The overwrite ID
type : :const:`disco.types.channel.PermissionsOverwriteType`
The overwrite type
allowed : :class:`PermissionValue`
All allowed permissions
denied : :class:`PermissionValue`
All denied permissions
"""
id = Field(snowflake)
type = Field(enum(PermissionOverwriteType))
allow = Field(PermissionValue, cast=int)
deny = Field(PermissionValue, cast=int)
channel_id = Field(snowflake)
@classmethod
def create(cls, channel, entity, allow=0, deny=0):
from disco.types.guild import Role
ptype = PermissionOverwriteType.ROLE if isinstance(entity, Role) else PermissionOverwriteType.MEMBER
return cls(
client=channel.client,
id=entity.id,
type=ptype,
allow=allow,
deny=deny,
channel_id=channel.id
).save()
@property
def compiled(self):
value = PermissionValue()
value -= self.deny
value += self.allow
return value
def save(self):
self.client.api.channels_permissions_modify(self.channel_id,
self.id,
self.allow.value or 0,
self.deny.value or 0,
self.type.name)
return self
def delete(self):
self.client.api.channels_permissions_delete(self.channel_id, self.id)
class Channel(SlottedModel, Permissible):
"""
Represents a Discord Channel.
Attributes
----------
id : snowflake
The channel ID.
guild_id : Optional[snowflake]
The guild id this channel is part of.
name : str
The channel's name.
topic : str
The channel's topic.
position : int
The channel's position.
bitrate : int
The channel's bitrate.
recipients: list(:class:`disco.types.user.User`)
Members of this channel (if this is a DM channel).
type : :const:`ChannelType`
The type of this channel.
overwrites : dict(snowflake, :class:`disco.types.channel.PermissionOverwrite`)
Channel permissions overwrites.
"""
id = Field(snowflake)
guild_id = Field(snowflake)
name = Field(text)
topic = Field(text)
last_message_id = Field(snowflake)
position = Field(int)
bitrate = Field(int)
recipients = AutoDictField(User, 'id')
type = Field(enum(ChannelType))
overwrites = AutoDictField(PermissionOverwrite, 'id', alias='permission_overwrites')
def __init__(self, *args, **kwargs):
super(Channel, self).__init__(*args, **kwargs)
self.after_load()
def after_load(self):
# TODO: hackfix
self.attach(six.itervalues(self.overwrites), {'channel_id': self.id, 'channel': self})
def __str__(self):
return u'#{}'.format(self.name)
def __repr__(self):
return u'<Channel {} ({})>'.format(self.id, self)
def get_permissions(self, user):
"""
Get the permissions a user has in the channel.
Returns
-------
:class:`disco.types.permissions.PermissionValue`
Computed permission value for the user.
"""
if not self.guild_id:
return Permissions.ADMINISTRATOR
member = self.guild.members.get(user.id)
base = self.guild.get_permissions(user)
for ow in six.itervalues(self.overwrites):
if ow.id != user.id and ow.id not in member.roles:
continue
base -= ow.deny
base += ow.allow
return base
@property
def mention(self):
return '<#{}>'.format(self.id)
@property
def is_guild(self):
"""
Whether this channel belongs to a guild.
"""
return self.type in (ChannelType.GUILD_TEXT, ChannelType.GUILD_VOICE)
@property
def is_dm(self):
"""
Whether this channel is a DM (does not belong to a guild).
"""
return self.type in (ChannelType.DM, ChannelType.GROUP_DM)
@property
def is_voice(self):
"""
Whether this channel supports voice.
"""
return self.type in (ChannelType.GUILD_VOICE, ChannelType.GROUP_DM)
@property
def messages(self):
"""
a default :class:`MessageIterator` for the channel.
"""
return self.messages_iter()
@cached_property
def guild(self):
"""
Guild this channel belongs to (if relevant).
"""
return self.client.state.guilds.get(self.guild_id)
def messages_iter(self, **kwargs):
"""
Creates a new :class:`MessageIterator` for the channel with the given
keyword arguments.
"""
return MessageIterator(self.client, self, **kwargs)
def get_message(self, message):
return self.client.api.channels_messages_get(self.id, to_snowflake(message))
def get_invites(self):
"""
Returns
-------
list(:class:`disco.types.invite.Invite`)
All invites for this channel.
"""
return self.client.api.channels_invites_list(self.id)
def create_invite(self, *args, **kwargs):
from disco.types.invite import Invite
return Invite.create(self, *args, **kwargs)
def get_pins(self):
"""
Returns
-------
list(:class:`disco.types.message.Message`)
All pinned messages for this channel.
"""
return self.client.api.channels_pins_list(self.id)
def create_pin(self, message):
self.client.api.channels_pins_create(self.id, to_snowflake(message))
def delete_pin(self, message):
self.client.api.channels_pins_delete(self.id, to_snowflake(message))
def get_webhooks(self):
return self.client.api.channels_webhooks_list(self.id)
def create_webhook(self, name=None, avatar=None):
return self.client.api.channels_webhooks_create(self.id, name, avatar)
def send_message(self, content, nonce=None, tts=False, attachment=None, embed=None):
"""
Send a message in this channel.
Parameters
----------
content : str
The message contents to send.
nonce : Optional[snowflake]
The nonce to attach to the message.
tts : Optional[bool]
Whether this is a TTS message.
Returns
-------
:class:`disco.types.message.Message`
The created message.
"""
return self.client.api.channels_messages_create(self.id, content, nonce, tts, attachment, embed)
def connect(self, *args, **kwargs):
"""
Connect to this channel over voice.
"""
assert self.is_voice, 'Channel must support voice to connect'
vc = VoiceClient(self)
vc.connect(*args, **kwargs)
return vc
def create_overwrite(self, *args, **kwargs):
return PermissionOverwrite.create(self, *args, **kwargs)
def delete_message(self, message):
"""
Deletes a single message from this channel.
Args
----
message : snowflake|:class:`disco.types.message.Message`
The message to delete.
"""
self.client.api.channels_messages_delete(self.id, to_snowflake(message))
@one_or_many
def delete_messages(self, messages):
"""
Deletes a set of messages using the correct API route based on the number
of messages passed.
Args
----
messages : list[snowflake|:class:`disco.types.message.Message`]
List of messages (or message ids) to delete. All messages must originate
from this channel.
"""
messages = map(to_snowflake, messages)
if not messages:
return
if self.can(self.client.state.me, Permissions.MANAGE_MESSAGES) and len(messages) > 2:
for chunk in chunks(messages, 100):
self.client.api.channels_messages_delete_bulk(self.id, chunk)
else:
for msg in messages:
self.delete_message(msg)
def delete(self):
assert (self.is_dm or self.guild.can(self.client.state.me, Permissions.MANAGE_GUILD)), 'Invalid Permissions'
self.client.api.channels_delete(self.id)
def close(self):
"""
Closes a DM channel. This is intended as a safer version of `delete`,
enforcing that the channel is actually a DM.
"""
assert self.is_dm, 'Cannot close non-DM channel'
self.delete()
class MessageIterator(object):
"""
An iterator which supports scanning through the messages for a channel.
Parameters
----------
client : :class:`disco.client.Client`
The disco client instance to use when making requests.
channel : `Channel`
The channel to iterate within.
direction : :attr:`MessageIterator.Direction`
The direction in which this iterator will move.
bulk : bool
If true, this iterator will yield messages in list batches, otherwise each
message will be yield individually.
before : snowflake
The message to begin scanning at.
after : snowflake
The message to begin scanning at.
chunk_size : int
The number of messages to request per API call.
"""
Direction = Enum('UP', 'DOWN')
def __init__(self, client, channel, direction=Direction.UP, bulk=False, before=None, after=None, chunk_size=100):
self.client = client
self.channel = channel
self.direction = direction
self.bulk = bulk
self.before = before
self.after = after
self.chunk_size = chunk_size
self.last = None
self._buffer = []
if not any((before, after)) and self.direction == self.Direction.DOWN:
raise Exception('Must specify either before or after for downward seeking')
def fill(self):
"""
Fills the internal buffer up with :class:`disco.types.message.Message` objects from the API.
"""
self._buffer = self.client.api.channels_messages_list(
self.channel.id,
before=self.before,
after=self.after,
limit=self.chunk_size)
if not len(self._buffer):
raise StopIteration
self.after = None
self.before = None
if self.direction == self.Direction.UP:
self.before = self._buffer[-1].id
else:
self._buffer.reverse()
self.after = self._buffer[-1].id
def next(self):
return self.__next__()
def __iter__(self):
return self
def __next__(self):
if not len(self._buffer):
self.fill()
if self.bulk:
res = self._buffer
self._buffer = []
return res
else:
return self._buffer.pop()