conflict fixes

This commit is contained in:
iDutchy
2021-04-07 18:27:34 -05:00
94 changed files with 4512 additions and 3018 deletions

View File

@@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
"""
The MIT License (MIT)
Copyright (c) 2015-2020 Rapptz
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
@@ -29,7 +27,7 @@ import asyncio
import discord.abc
from .permissions import Permissions
from .enums import ChannelType, try_enum
from .enums import ChannelType, try_enum, VoiceRegion
from .mixins import Hashable
from . import utils
from .asset import Asset
@@ -38,6 +36,7 @@ from .errors import ClientException, NoMoreItems, InvalidArgument
__all__ = (
'TextChannel',
'VoiceChannel',
'StageChannel',
'DMChannel',
'CategoryChannel',
'StoreChannel',
@@ -114,7 +113,8 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
('news', self.is_news()),
('category_id', self.category_id)
]
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
joined = ' '.join('%s=%r' % t for t in attrs)
return f'<{self.__class__.__name__} {joined}>'
def _update(self, guild, data):
self.guild = guild
@@ -148,6 +148,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
.. versionadded:: 1.5.0.2"""
return self.permissions_for(self.guild.me).send_messages
@utils.copy_doc(discord.abc.GuildChannel.permissions_for)
def permissions_for(self, member):
base = super().permissions_for(member)
@@ -156,8 +157,6 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
base.value &= ~denied.value
return base
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
@property
def members(self):
"""List[:class:`Member`]: Returns all members that can see this channel."""
@@ -247,6 +246,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
"""
await self._edit(options, reason=reason)
@utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name=None, reason=None):
return await self._clone_impl({
'topic': self.topic,
@@ -254,8 +254,6 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
'rate_limit_per_user': self.slowmode_delay
}, name=name, reason=reason)
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
async def delete_messages(self, messages):
"""|coro|
@@ -320,10 +318,6 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
account). The :attr:`~Permissions.read_message_history` permission is
also needed to retrieve message history.
Internally, this employs a different number of strategies depending
on the conditions met such as if a bulk delete is possible or if
the account is a user bot or not.
Examples
---------
@@ -354,8 +348,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
bulk: :class:`bool`
If ``True``, use bulk delete. Setting this to ``False`` is useful for mass-deleting
a bot's own messages without :attr:`Permissions.manage_messages`. When ``True``, will
fall back to single delete if current account is a user bot, or if messages are
older than two weeks.
fall back to single delete if messages are older than two weeks.
Raises
-------
@@ -378,7 +371,7 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
count = 0
minimum_time = int((time.time() - 14 * 24 * 60 * 60) * 1000.0 - 1420070400000) << 22
strategy = self.delete_messages if self._state.is_bot and bulk else _single_delete_strategy
strategy = self.delete_messages if bulk else _single_delete_strategy
while True:
try:
@@ -546,7 +539,80 @@ class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
from .message import PartialMessage
return PartialMessage(channel=self, id=message_id)
class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
__slots__ = ('name', 'id', 'guild', 'bitrate', 'user_limit',
'_state', 'position', '_overwrites', 'category_id',
'rtc_region')
def __init__(self, *, state, guild, data):
self._state = state
self.id = int(data['id'])
self._update(guild, data)
def _get_voice_client_key(self):
return self.guild.id, 'guild_id'
def _get_voice_state_pair(self):
return self.guild.id, self.id
def _update(self, guild, data):
self.guild = guild
self.name = data['name']
self.rtc_region = data.get('rtc_region')
if self.rtc_region:
self.rtc_region = try_enum(VoiceRegion, self.rtc_region)
self.category_id = utils._get_as_snowflake(data, 'parent_id')
self.position = data['position']
self.bitrate = data.get('bitrate')
self.user_limit = data.get('user_limit')
self._fill_overwrites(data)
@property
def _sorting_bucket(self):
return ChannelType.voice.value
@property
def members(self):
"""List[:class:`Member`]: Returns all members that are currently inside this voice channel."""
ret = []
for user_id, state in self.guild._voice_states.items():
if state.channel and state.channel.id == self.id:
member = self.guild.get_member(user_id)
if member is not None:
ret.append(member)
return ret
@property
def voice_states(self):
"""Returns a mapping of member IDs who have voice states in this channel.
.. versionadded:: 1.3
.. note::
This function is intentionally low level to replace :attr:`members`
when the member cache is unavailable.
Returns
--------
Mapping[:class:`int`, :class:`VoiceState`]
The mapping of member ID to a voice state.
"""
return {key: value for key, value in self.guild._voice_states.items() if value.channel.id == self.id}
@utils.copy_doc(discord.abc.GuildChannel.permissions_for)
def permissions_for(self, member):
base = super().permissions_for(member)
# voice channels cannot be edited by people who can't connect to them
# It also implicitly denies all other voice perms
if not base.connect:
denied = Permissions.voice()
denied.update(manage_channels=True, manage_roles=True)
base.value &= ~denied.value
return base
class VoiceChannel(VocalGuildChannel):
"""Represents a Discord guild voice channel.
.. container:: operations
@@ -584,101 +650,40 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
The channel's preferred audio bitrate in bits per second.
user_limit: :class:`int`
The channel's limit for number of members that can be in a voice channel.
rtc_region: Optional[:class:`VoiceRegion`]
The region for the voice channel's voice communication.
A value of ``None`` indicates automatic voice region detection.
.. versionadded:: 1.7
"""
__slots__ = ('name', 'id', 'guild', 'bitrate', 'user_limit',
'_state', 'position', '_overwrites', 'category_id')
def __init__(self, *, state, guild, data):
self._state = state
self.id = int(data['id'])
self._update(guild, data)
__slots__ = ()
def __repr__(self):
attrs = [
('id', self.id),
('name', self.name),
('rtc_region', self.rtc_region),
('position', self.position),
('bitrate', self.bitrate),
('user_limit', self.user_limit),
('category_id', self.category_id)
]
return '<%s %s>' % (self.__class__.__name__, ' '.join('%s=%r' % t for t in attrs))
def _get_voice_client_key(self):
return self.guild.id, 'guild_id'
def _get_voice_state_pair(self):
return self.guild.id, self.id
joined = ' '.join('%s=%r' % t for t in attrs)
return f'<{self.__class__.__name__} {joined}>'
@property
def type(self):
""":class:`ChannelType`: The channel's Discord type."""
return ChannelType.voice
def _update(self, guild, data):
self.guild = guild
self.name = data['name']
self.category_id = utils._get_as_snowflake(data, 'parent_id')
self.position = data['position']
self.bitrate = data.get('bitrate')
self.user_limit = data.get('user_limit')
self._fill_overwrites(data)
@property
def _sorting_bucket(self):
return ChannelType.voice.value
@property
def members(self):
"""List[:class:`Member`]: Returns all members that are currently inside this voice channel."""
ret = []
for user_id, state in self.guild._voice_states.items():
if state.channel.id == self.id:
member = self.guild.get_member(user_id)
if member is not None:
ret.append(member)
return ret
@property
def voice_states(self):
"""Returns a mapping of member IDs who have voice states in this channel.
.. versionadded:: 1.3
.. note::
This function is intentionally low level to replace :attr:`members`
when the member cache is unavailable.
Returns
--------
Mapping[:class:`int`, :class:`VoiceState`]
The mapping of member ID to a voice state.
"""
return {key: value for key, value in self.guild._voice_states.items() if value.channel.id == self.id}
def permissions_for(self, member):
base = super().permissions_for(member)
# voice channels cannot be edited by people who can't connect to them
# It also implicitly denies all other voice perms
if not base.connect:
denied = Permissions.voice()
denied.update(manage_channels=True, manage_roles=True)
base.value &= ~denied.value
return base
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
@utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name=None, reason=None):
return await self._clone_impl({
'bitrate': self.bitrate,
'user_limit': self.user_limit
}, name=name, reason=reason)
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
async def edit(self, *, reason=None, **options):
"""|coro|
@@ -711,6 +716,136 @@ class VoiceChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
overwrites: :class:`dict`
A :class:`dict` of target (either a role or a member) to
:class:`PermissionOverwrite` to apply to the channel.
rtc_region: Optional[:class:`VoiceRegion`]
The new region for the voice channel's voice communication.
A value of ``None`` indicates automatic voice region detection.
.. versionadded:: 1.7
Raises
------
InvalidArgument
If the permission overwrite information is not in proper form.
Forbidden
You do not have permissions to edit the channel.
HTTPException
Editing the channel failed.
"""
await self._edit(options, reason=reason)
class StageChannel(VocalGuildChannel):
"""Represents a Discord guild stage channel.
.. versionadded:: 1.7
.. container:: operations
.. describe:: x == y
Checks if two channels are equal.
.. describe:: x != y
Checks if two channels are not equal.
.. describe:: hash(x)
Returns the channel's hash.
.. describe:: str(x)
Returns the channel's name.
Attributes
-----------
name: :class:`str`
The channel name.
guild: :class:`Guild`
The guild the channel belongs to.
id: :class:`int`
The channel ID.
topic: Optional[:class:`str`]
The channel's topic. ``None`` if it isn't set.
category_id: Optional[:class:`int`]
The category channel ID this channel belongs to, if applicable.
position: :class:`int`
The position in the channel list. This is a number that starts at 0. e.g. the
top channel is position 0.
bitrate: :class:`int`
The channel's preferred audio bitrate in bits per second.
user_limit: :class:`int`
The channel's limit for number of members that can be in a stage channel.
rtc_region: Optional[:class:`VoiceRegion`]
The region for the stage channel's voice communication.
A value of ``None`` indicates automatic voice region detection.
"""
__slots__ = ('topic',)
def __repr__(self):
attrs = [
('id', self.id),
('name', self.name),
('topic', self.topic),
('rtc_region', self.rtc_region),
('position', self.position),
('bitrate', self.bitrate),
('user_limit', self.user_limit),
('category_id', self.category_id)
]
joined = ' '.join('%s=%r' % t for t in attrs)
return f'<{self.__class__.__name__} {joined}>'
def _update(self, guild, data):
super()._update(guild, data)
self.topic = data.get('topic')
@property
def requesting_to_speak(self):
"""List[:class:`Member`]: A list of members who are requesting to speak in the stage channel."""
return [member for member in self.members if member.voice.requested_to_speak_at is not None]
@property
def type(self):
""":class:`ChannelType`: The channel's Discord type."""
return ChannelType.stage_voice
@utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name=None, reason=None):
return await self._clone_impl({
'topic': self.topic,
}, name=name, reason=reason)
async def edit(self, *, reason=None, **options):
"""|coro|
Edits the channel.
You must have the :attr:`~Permissions.manage_channels` permission to
use this.
Parameters
----------
name: :class:`str`
The new channel's name.
topic: :class:`str`
The new channel's topic.
position: :class:`int`
The new channel's position.
sync_permissions: :class:`bool`
Whether to sync permissions with the channel's new or pre-existing
category. Defaults to ``False``.
category: Optional[:class:`CategoryChannel`]
The new category for this channel. Can be ``None`` to remove the
category.
reason: Optional[:class:`str`]
The reason for editing this channel. Shows up on the audit log.
overwrites: :class:`dict`
A :class:`dict` of target (either a role or a member) to
:class:`PermissionOverwrite` to apply to the channel.
rtc_region: Optional[:class:`VoiceRegion`]
The new region for the stage channel's voice communication.
A value of ``None`` indicates automatic voice region detection.
Raises
------
@@ -791,13 +926,12 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
""":class:`bool`: Checks if the category is NSFW."""
return self.nsfw
@utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name=None, reason=None):
return await self._clone_impl({
'nsfw': self.nsfw
}, name=name, reason=reason)
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
async def edit(self, *, reason=None, **options):
"""|coro|
@@ -835,6 +969,11 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
await self._edit(options=options, reason=reason)
@utils.copy_doc(discord.abc.GuildChannel.move)
async def move(self, **kwargs):
kwargs.pop('category', None)
await super().move(**kwargs)
@property
def channels(self):
"""List[:class:`abc.GuildChannel`]: Returns the channels that are under this category.
@@ -866,6 +1005,18 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
ret.sort(key=lambda c: (c.position, c.id))
return ret
@property
def stage_channels(self):
"""List[:class:`StageChannel`]: Returns the voice channels that are under this category.
.. versionadded:: 1.7
"""
ret = [c for c in self.guild.channels
if c.category_id == self.id
and isinstance(c, StageChannel)]
ret.sort(key=lambda c: (c.position, c.id))
return ret
async def create_text_channel(self, name, *, overwrites=None, reason=None, **options):
"""|coro|
@@ -890,6 +1041,20 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
"""
return await self.guild.create_voice_channel(name, overwrites=overwrites, category=self, reason=reason, **options)
async def create_stage_channel(self, name, *, overwrites=None, reason=None, **options):
"""|coro|
A shortcut method to :meth:`Guild.create_stage_channel` to create a :class:`StageChannel` in the category.
.. versionadded:: 1.7
Returns
-------
:class:`StageChannel`
The channel that was just created.
"""
return await self.guild.create_stage_channel(name, overwrites=overwrites, category=self, reason=reason, **options)
class StoreChannel(discord.abc.GuildChannel, Hashable):
"""Represents a Discord guild store channel.
@@ -953,6 +1118,7 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
""":class:`ChannelType`: The channel's Discord type."""
return ChannelType.store
@utils.copy_doc(discord.abc.GuildChannel.permissions_for)
def permissions_for(self, member):
base = super().permissions_for(member)
@@ -961,19 +1127,16 @@ class StoreChannel(discord.abc.GuildChannel, Hashable):
base.value &= ~denied.value
return base
permissions_for.__doc__ = discord.abc.GuildChannel.permissions_for.__doc__
def is_nsfw(self):
""":class:`bool`: Checks if the channel is NSFW."""
return self.nsfw
@utils.copy_doc(discord.abc.GuildChannel.clone)
async def clone(self, *, name=None, reason=None):
return await self._clone_impl({
'nsfw': self.nsfw
}, name=name, reason=reason)
clone.__doc__ = discord.abc.GuildChannel.clone.__doc__
async def edit(self, *, reason=None, **options):
"""|coro|
@@ -1059,7 +1222,7 @@ class DMChannel(discord.abc.Messageable, Hashable):
return self
def __str__(self):
return 'Direct Message with %s' % self.recipient
return f'Direct Message with {self.recipient}'
def __repr__(self):
return '<DMChannel id={0.id} recipient={0.recipient!r}>'.format(self)
@@ -1255,8 +1418,8 @@ class GroupChannel(discord.abc.Messageable, Hashable):
This returns all the Text related permissions set to ``True`` except:
- send_tts_messages: You cannot send TTS messages in a DM.
- manage_messages: You cannot delete others messages in a DM.
- :attr:`~Permissions.send_tts_messages`: You cannot send TTS messages in a DM.
- :attr:`~Permissions.manage_messages`: You cannot delete others messages in a DM.
This also checks the kick_members permission if the user is the owner.
@@ -1281,86 +1444,6 @@ class GroupChannel(discord.abc.Messageable, Hashable):
return base
async def add_recipients(self, *recipients):
r"""|coro|
Adds recipients to this group.
A group can only have a maximum of 10 members.
Attempting to add more ends up in an exception. To
add a recipient to the group, you must have a relationship
with the user of type :attr:`RelationshipType.friend`.
Parameters
-----------
\*recipients: :class:`User`
An argument list of users to add to this group.
Raises
-------
HTTPException
Adding a recipient to this group failed.
"""
# TODO: wait for the corresponding WS event
req = self._state.http.add_group_recipient
for recipient in recipients:
await req(self.id, recipient.id)
async def remove_recipients(self, *recipients):
r"""|coro|
Removes recipients from this group.
Parameters
-----------
\*recipients: :class:`User`
An argument list of users to remove from this group.
Raises
-------
HTTPException
Removing a recipient from this group failed.
"""
# TODO: wait for the corresponding WS event
req = self._state.http.remove_group_recipient
for recipient in recipients:
await req(self.id, recipient.id)
async def edit(self, **fields):
"""|coro|
Edits the group.
Parameters
-----------
name: Optional[:class:`str`]
The new name to change the group to.
Could be ``None`` to remove the name.
icon: Optional[:class:`bytes`]
A :term:`py:bytes-like object` representing the new icon.
Could be ``None`` to remove the icon.
Raises
-------
HTTPException
Editing the group failed.
"""
try:
icon_bytes = fields['icon']
except KeyError:
pass
else:
if icon_bytes is not None:
fields['icon'] = utils._bytes_to_base64_data(icon_bytes)
data = await self._state.http.edit_group(self.id, **fields)
self._update_group(data)
async def leave(self):
"""|coro|
@@ -1392,5 +1475,7 @@ def _channel_factory(channel_type):
return TextChannel, value
elif value is ChannelType.store:
return StoreChannel, value
elif value is ChannelType.stage_voice:
return StageChannel, value
else:
return None, value