mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-21 00:07:51 +00:00
Make roles and guilds stateful.
This commit is contained in:
parent
d1d54a468a
commit
a7a60e433b
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user