mirror of
				https://github.com/Rapptz/discord.py.git
				synced 2025-11-04 15:33:08 +00:00 
			
		
		
		
	Make roles and guilds stateful.
This commit is contained in:
		
							
								
								
									
										309
									
								
								discord/guild.py
									
									
									
									
									
								
							
							
						
						
									
										309
									
								
								discord/guild.py
									
									
									
									
									
								
							@@ -24,6 +24,9 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 | 
			
		||||
DEALINGS IN THE SOFTWARE.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import asyncio
 | 
			
		||||
 | 
			
		||||
from . import utils
 | 
			
		||||
from .role import Role
 | 
			
		||||
from .member import Member, VoiceState
 | 
			
		||||
@@ -32,8 +35,8 @@ from .game import Game
 | 
			
		||||
from .channel import *
 | 
			
		||||
from .enums import GuildRegion, Status, ChannelType, try_enum, VerificationLevel
 | 
			
		||||
from .mixins import Hashable
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
from .user import User
 | 
			
		||||
from .invite import Invite
 | 
			
		||||
 | 
			
		||||
class Guild(Hashable):
 | 
			
		||||
    """Represents a Discord guild.
 | 
			
		||||
@@ -375,3 +378,305 @@ class Guild(Hashable):
 | 
			
		||||
            return m.nick == name or m.name == name
 | 
			
		||||
 | 
			
		||||
        return utils.find(pred, members)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def leave(self):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Leaves the guild.
 | 
			
		||||
 | 
			
		||||
        Note
 | 
			
		||||
        --------
 | 
			
		||||
        You cannot leave the guild that you own, you must delete it instead
 | 
			
		||||
        via :meth:`delete`.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        --------
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Leaving the guild failed.
 | 
			
		||||
        """
 | 
			
		||||
        yield from self._state.http.leave_guild(self.id)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Deletes the guild. You must be the guild owner to delete the
 | 
			
		||||
        guild.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        --------
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Deleting the guild failed.
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have permissions to delete the guild.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        yield from self._state.http.delete_guild(self.id)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def edit(self, **fields):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Edits the guild.
 | 
			
		||||
 | 
			
		||||
        You must have the :attr:`Permissions.manage_guild` permission
 | 
			
		||||
        to edit the guild.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ----------
 | 
			
		||||
        name: str
 | 
			
		||||
            The new name of the guild.
 | 
			
		||||
        icon: bytes
 | 
			
		||||
            A *bytes-like* object representing the icon. Only PNG/JPEG supported.
 | 
			
		||||
            Could be ``None`` to denote removal of the icon.
 | 
			
		||||
        region: :class:`GuildRegion`
 | 
			
		||||
            The new region for the guild's voice communication.
 | 
			
		||||
        afk_channel: :class:`VoiceChannel`
 | 
			
		||||
            The new channel that is the AFK channel. Could be ``None`` for no AFK channel.
 | 
			
		||||
        afk_timeout: int
 | 
			
		||||
            The number of seconds until someone is moved to the AFK channel.
 | 
			
		||||
        owner: :class:`Member`
 | 
			
		||||
            The new owner of the guild to transfer ownership to. Note that you must
 | 
			
		||||
            be owner of the guild to do this.
 | 
			
		||||
        verification_level: :class:`VerificationLevel`
 | 
			
		||||
            The new verification level for the guild.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have permissions to edit the guild.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Editing the guild failed.
 | 
			
		||||
        InvalidArgument
 | 
			
		||||
            The image format passed in to ``icon`` is invalid. It must be
 | 
			
		||||
            PNG or JPG. This is also raised if you are not the owner of the
 | 
			
		||||
            guild and request an ownership transfer.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            icon_bytes = fields['icon']
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            icon = self.icon
 | 
			
		||||
        else:
 | 
			
		||||
            if icon_bytes is not None:
 | 
			
		||||
                icon = utils._bytes_to_base64_data(icon_bytes)
 | 
			
		||||
            else:
 | 
			
		||||
                icon = None
 | 
			
		||||
 | 
			
		||||
        fields['icon'] = icon
 | 
			
		||||
        if 'afk_channel' in fields:
 | 
			
		||||
            fields['afk_channel_id'] = fields['afk_channel'].id
 | 
			
		||||
 | 
			
		||||
        if 'owner' in fields:
 | 
			
		||||
            if self.owner != self.me:
 | 
			
		||||
                raise InvalidArgument('To transfer ownership you must be the owner of the guild.')
 | 
			
		||||
 | 
			
		||||
            fields['owner_id'] = fields['owner'].id
 | 
			
		||||
 | 
			
		||||
        if 'region' in fields:
 | 
			
		||||
            fields['region'] = str(fields['region'])
 | 
			
		||||
 | 
			
		||||
        level = fields.get('verification_level', self.verification_level)
 | 
			
		||||
        if not isinstance(level, VerificationLevel):
 | 
			
		||||
            raise InvalidArgument('verification_level field must of type VerificationLevel')
 | 
			
		||||
 | 
			
		||||
        fields['verification_level'] = level.value
 | 
			
		||||
        yield from self._state.http.edit_guild(self.id, **fields)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def bans(self):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Retrieves all the :class:`User`\s that are banned from the guild.
 | 
			
		||||
 | 
			
		||||
        You must have :attr:`Permissions.ban_members` permission
 | 
			
		||||
        to get this information.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have proper permissions to get the information.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            An error occurred while fetching the information.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        --------
 | 
			
		||||
        list
 | 
			
		||||
            A list of :class:`User` that have been banned.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        data = yield from self._state.http.get_bans(self.id)
 | 
			
		||||
        return [User(state=self._state, data=user) for user in data]
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def prune_members(self, *, days):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Prunes the guild from its inactive members.
 | 
			
		||||
 | 
			
		||||
        The inactive members are denoted if they have not logged on in
 | 
			
		||||
        ``days`` number of days and they have no roles.
 | 
			
		||||
 | 
			
		||||
        You must have the :attr:`Permissions.kick_members` permission
 | 
			
		||||
        to use this.
 | 
			
		||||
 | 
			
		||||
        To check how many members you would prune without actually pruning,
 | 
			
		||||
        see the :meth:`estimate_pruned_members` function.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        -----------
 | 
			
		||||
        days: int
 | 
			
		||||
            The number of days before counting as inactive.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have permissions to prune members.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            An error occurred while pruning members.
 | 
			
		||||
        InvalidArgument
 | 
			
		||||
            An integer was not passed for ``days``.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        ---------
 | 
			
		||||
        int
 | 
			
		||||
            The number of members pruned.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not isinstance(days, int):
 | 
			
		||||
            raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
 | 
			
		||||
 | 
			
		||||
        data = yield from self._state.http.prune_members(self.id, days)
 | 
			
		||||
        return data['pruned']
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def estimate_pruned_members(self, *, days):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Similar to :meth:`prune_members` except instead of actually
 | 
			
		||||
        pruning members, it returns how many members it would prune
 | 
			
		||||
        from the guild had it been called.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        -----------
 | 
			
		||||
        days: int
 | 
			
		||||
            The number of days before counting as inactive.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have permissions to prune members.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            An error occurred while fetching the prune members estimate.
 | 
			
		||||
        InvalidArgument
 | 
			
		||||
            An integer was not passed for ``days``.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        ---------
 | 
			
		||||
        int
 | 
			
		||||
            The number of members estimated to be pruned.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if not isinstance(days, int):
 | 
			
		||||
            raise InvalidArgument('Expected int for ``days``, received {0.__class__.__name__} instead.'.format(days))
 | 
			
		||||
 | 
			
		||||
        data = yield from self._state.http.estimate_pruned_members(self.id, days)
 | 
			
		||||
        return data['pruned']
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def invites(self):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Returns a list of all active instant invites from the guild.
 | 
			
		||||
 | 
			
		||||
        You must have :attr:`Permissions.manage_guild` to get this information.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have proper permissions to get the information.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            An error occurred while fetching the information.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        -------
 | 
			
		||||
        list of :class:`Invite`
 | 
			
		||||
            The list of invites that are currently active.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        data = yield from self._state.http.invites_from(guild.id)
 | 
			
		||||
        result = []
 | 
			
		||||
        for invite in data:
 | 
			
		||||
            channel = self.get_channel(int(invite['channel']['id']))
 | 
			
		||||
            invite['channel'] = channel
 | 
			
		||||
            invite['guild'] = self
 | 
			
		||||
            result.append(Invite(state=self._state, data=invite))
 | 
			
		||||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def create_custom_emoji(self, *, name, image):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Creates a custom :class:`Emoji` for the guild.
 | 
			
		||||
 | 
			
		||||
        This endpoint is only allowed for user bots or white listed
 | 
			
		||||
        bots. If this is done by a user bot then this is a local
 | 
			
		||||
        emoji that can only be used inside the guild. If done by
 | 
			
		||||
        a whitelisted bot, then this emoji is "global".
 | 
			
		||||
 | 
			
		||||
        There is currently a limit of 50 local emotes per guild.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        -----------
 | 
			
		||||
        name: str
 | 
			
		||||
            The emoji name. Must be at least 2 characters.
 | 
			
		||||
        image: bytes
 | 
			
		||||
            The *bytes-like* object representing the image data to use.
 | 
			
		||||
            Only JPG and PNG images are supported.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        --------
 | 
			
		||||
        :class:`Emoji`
 | 
			
		||||
            The created emoji.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You are not allowed to create emojis.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            An error occurred creating an emoji.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        img = utils._bytes_to_base64_data(image)
 | 
			
		||||
        data = yield from self._state.http.create_custom_emoji(self.id, name, img)
 | 
			
		||||
        return Emoji(guild=self, data=data, state=self._state)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def create_role(self, **fields):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Creates a :class:`Role` for the guild.
 | 
			
		||||
 | 
			
		||||
        This function is similar to :meth:`Role.edit` in both
 | 
			
		||||
        the fields taken and exceptions thrown.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        --------
 | 
			
		||||
        :class:`Role`
 | 
			
		||||
            The newly created role.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        data = yield from self._state.http.create_role(self.id)
 | 
			
		||||
        role = Role(guild=self, data=data, state=self._state)
 | 
			
		||||
 | 
			
		||||
        if fields:
 | 
			
		||||
            # we have to call edit because you can't pass a payload to the
 | 
			
		||||
            # http request currently.
 | 
			
		||||
            yield from role.edit(**fields)
 | 
			
		||||
 | 
			
		||||
        # TODO: add to cache
 | 
			
		||||
        return role
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										108
									
								
								discord/role.py
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								discord/role.py
									
									
									
									
									
								
							@@ -24,6 +24,8 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 | 
			
		||||
DEALINGS IN THE SOFTWARE.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
 | 
			
		||||
from .permissions import Permissions
 | 
			
		||||
from .colour import Colour
 | 
			
		||||
from .mixins import Hashable
 | 
			
		||||
@@ -144,3 +146,109 @@ class Role(Hashable):
 | 
			
		||||
    def mention(self):
 | 
			
		||||
        """Returns a string that allows you to mention a role."""
 | 
			
		||||
        return '<@&{}>'.format(self.id)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def _move(self, position):
 | 
			
		||||
        if position <= 0:
 | 
			
		||||
            raise InvalidArgument("Cannot move role to position 0 or below")
 | 
			
		||||
 | 
			
		||||
        if self.is_everyone:
 | 
			
		||||
            raise InvalidArgument("Cannot move default role")
 | 
			
		||||
 | 
			
		||||
        if self.position == position:
 | 
			
		||||
            return  # Save discord the extra request.
 | 
			
		||||
 | 
			
		||||
        http = self._state.http
 | 
			
		||||
        url = '{0}/{1}/roles'.format(http.GUILDS, self.guild.id)
 | 
			
		||||
 | 
			
		||||
        change_range = range(min(self.position, position), max(self.position, position) + 1)
 | 
			
		||||
        sorted_roles = sorted((x for x in self.guild.roles if x.position in change_range and x.id != self.id),
 | 
			
		||||
                              key=lambda x: x.position)
 | 
			
		||||
 | 
			
		||||
        roles = [r.id for r in sorted_roles]
 | 
			
		||||
 | 
			
		||||
        if self.position > position:
 | 
			
		||||
            roles.insert(0, self.id)
 | 
			
		||||
        else:
 | 
			
		||||
            roles.append(self.id)
 | 
			
		||||
 | 
			
		||||
        payload = [{"id": z[0], "position": z[1]} for z in zip(roles, change_range)]
 | 
			
		||||
        yield from http.patch(url, json=payload, bucket='move_role')
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def edit(self, **fields):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Edits the role.
 | 
			
		||||
 | 
			
		||||
        You must have the :attr:`Permissions.manage_roles` permission to
 | 
			
		||||
        use this.
 | 
			
		||||
 | 
			
		||||
        All fields are optional.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        -----------
 | 
			
		||||
        name: str
 | 
			
		||||
            The new role name to change to.
 | 
			
		||||
        permissions: :class:`Permissions`
 | 
			
		||||
            The new permissions to change to.
 | 
			
		||||
        colour: :class:`Colour`
 | 
			
		||||
            The new colour to change to. (aliased to color as well)
 | 
			
		||||
        hoist: bool
 | 
			
		||||
            Indicates if the role should be shown separately in the member list.
 | 
			
		||||
        mentionable: bool
 | 
			
		||||
            Indicates if the role should be mentionable by others.
 | 
			
		||||
        position: int
 | 
			
		||||
            The new role's position. This must be below your top role's
 | 
			
		||||
            position or it will fail.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have permissions to change the role.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Editing the role failed.
 | 
			
		||||
        InvalidArgument
 | 
			
		||||
            An invalid position was given or the default
 | 
			
		||||
            role was asked to be moved.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        position = fields.get('position')
 | 
			
		||||
        if position is not None:
 | 
			
		||||
            yield from self._move(position)
 | 
			
		||||
            self.position = position
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            colour = fields['colour']
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            colour = fields.get('color', self.colour)
 | 
			
		||||
 | 
			
		||||
        payload = {
 | 
			
		||||
            'name': fields.get('name', self.name),
 | 
			
		||||
            'permissions': fields.get('permissions', self.permissions).value,
 | 
			
		||||
            'color': colour.value,
 | 
			
		||||
            'hoist': fields.get('hoist', self.hoist),
 | 
			
		||||
            'mentionable': fields.get('mentionable', self.mentionable)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        data = yield from self._state.http.edit_role(self.guild.id, self.id, **payload)
 | 
			
		||||
        self._update(data)
 | 
			
		||||
 | 
			
		||||
    @asyncio.coroutine
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Deletes the role.
 | 
			
		||||
 | 
			
		||||
        You must have the :attr:`Permissions.manage_roles` permission to
 | 
			
		||||
        use this.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        --------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have permissions to delete the role.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Deleting the role failed.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        yield from self._state.http.delete_role(self.guild.id, self.id)
 | 
			
		||||
 
 | 
			
		||||
@@ -589,7 +589,7 @@ class ConnectionState:
 | 
			
		||||
    def parse_guild_role_delete(self, data):
 | 
			
		||||
        guild = self._get_guild(int(data['guild_id']))
 | 
			
		||||
        if guild is not None:
 | 
			
		||||
            role_id = data.get('role_id')
 | 
			
		||||
            role_id = int(data['role_id'])
 | 
			
		||||
            role = utils.find(lambda r: r.id == role_id, guild.roles)
 | 
			
		||||
            try:
 | 
			
		||||
                guild._remove_role(role)
 | 
			
		||||
@@ -602,7 +602,7 @@ class ConnectionState:
 | 
			
		||||
        guild = self._get_guild(int(data['guild_id']))
 | 
			
		||||
        if guild is not None:
 | 
			
		||||
            role_data = data['role']
 | 
			
		||||
            role_id = role_data['id']
 | 
			
		||||
            role_id = int(role_data['id'])
 | 
			
		||||
            role = utils.find(lambda r: r.id == role_id, guild.roles)
 | 
			
		||||
            if role is not None:
 | 
			
		||||
                old_role = copy.copy(role)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user