from typing import TYPE_CHECKING, Union, Optional
from . import utils
from .asset import Asset
from .colour import Colour
from .file import File
from .flags import Permissions, PermissionType
from .object import PartialBase, Snowflake
if TYPE_CHECKING:
from .guild import PartialGuild, Guild
from .http import DiscordAPI
MISSING = utils.MISSING
__all__ = (
"PartialRole",
"Role",
)
[docs]
class PartialRole(PartialBase):
def __init__(
self,
*,
state: "DiscordAPI",
id: int,
guild_id: int
):
super().__init__(id=int(id))
self._state = state
self._target_type: PermissionType = PermissionType.role
self.guild_id: int = guild_id
def __repr__(self) -> str:
return f"<PartialRole id={self.id} guild_id={self.guild_id}>"
@property
def guild(self) -> "Guild | PartialGuild":
""" `PartialGuild`: Returns the guild this role is in """
cache = self._state.cache.get_guild(self.guild_id)
if cache:
return cache
from .guild import PartialGuild
return PartialGuild(state=self._state, id=self.guild_id)
@property
def mention(self) -> str:
""" `str`: Returns a string that mentions the role """
return f"<@&{self.id}>"
[docs]
async def add_role(
self,
user_id: Snowflake | int,
*,
reason: Optional[str] = None
) -> None:
"""
Add the role to someone
Parameters
----------
user_id: `int`
The user ID to add the role to
reason: `Optional[str]`
The reason for adding the role
"""
await self._state.query(
"PUT",
f"/guilds/{self.guild_id}/members/{int(user_id)}/roles/{self.id}",
res_method="text",
reason=reason
)
[docs]
async def remove_role(
self,
user_id: Snowflake | int,
*,
reason: Optional[str] = None
) -> None:
"""
Remove the role from someone
Parameters
----------
user_id: `int`
The user ID to remove the role from
reason: `Optional[str]`
The reason for removing the role
"""
await self._state.query(
"DELETE",
f"/guilds/{self.guild_id}/members/{int(user_id)}/roles/{self.id}",
res_method="text",
reason=reason
)
[docs]
async def delete(
self,
*,
reason: Optional[str] = None
) -> None:
"""
Delete the role
Parameters
----------
reason: `Optional[str]`
The reason for deleting the role
"""
await self._state.query(
"DELETE",
f"/guilds/{self.guild_id}/roles/{self.id}",
reason=reason,
res_method="text"
)
[docs]
async def edit(
self,
*,
name: Optional[str] = MISSING,
colour: Optional[Union[Colour, int]] = MISSING,
hoist: Optional[bool] = MISSING,
mentionable: Optional[bool] = MISSING,
positions: Optional[int] = MISSING,
permissions: Optional["Permissions"] = MISSING,
unicode_emoji: Optional[str] = MISSING,
icon: Optional[Union[File, bytes]] = MISSING,
reason: Optional[str] = None,
) -> "Role":
"""
Edit the role
Parameters
----------
name: `Optional[str]`
The new name of the role
colour: `Optional[Union[Colour, int]]`
The new colour of the role
hoist: `Optional[bool]`
Whether the role should be displayed separately in the sidebar
mentionable: `Optional[bool]`
Whether the role should be mentionable
unicode_emoji: `Optional[str]`
The new unicode emoji of the role
positions: `Optional[int]`
The new position of the role
permissions: `Optional[Permissions]`
The new permissions for the role
icon: `Optional[File]`
The new icon of the role
reason: `Union[str]`
The reason for editing the role
Returns
-------
`Union[Role, PartialRole]`
The edited role and its data
Raises
------
`ValueError`
- If both `unicode_emoji` and `icon` are set
- If there were no changes applied to the role
- If position was changed, but Discord API returned invalid data
"""
payload = {}
_role: Optional["Role"] = None
if name is not MISSING:
payload["name"] = name
if colour is not MISSING:
if isinstance(colour, Colour):
payload["color"] = colour.value
else:
payload["color"] = colour
if permissions is not MISSING:
payload["permissions"] = permissions.value
if hoist is not MISSING:
payload["hoist"] = hoist
if mentionable is not MISSING:
payload["mentionable"] = mentionable
if unicode_emoji is not MISSING:
payload["unicode_emoji"] = unicode_emoji
if icon is not MISSING:
payload["icon"] = (
utils.bytes_to_base64(icon)
if icon else None
)
if (
unicode_emoji is not MISSING and
icon is not MISSING
):
raise ValueError("Cannot set both unicode_emoji and icon")
if positions is not MISSING:
r = await self._state.query(
"PATCH",
f"/guilds/{self.guild_id}/roles",
json={
"id": str(self.id),
"position": positions
},
reason=reason
)
find_role: Optional[dict] = next((
r for r in r.response
if r["id"] == str(self.id)
), None)
if not find_role:
raise ValueError(
"Could not find role in response "
"(Most likely Discord API bug)"
)
_role = Role(
state=self._state,
guild=self.guild,
data=find_role
)
if payload:
r = await self._state.query(
"PATCH",
f"/guilds/{self.guild_id}/roles/{self.id}",
json=payload,
reason=reason
)
_role = Role(
state=self._state,
guild=self.guild,
data=r.response
)
if not _role:
raise ValueError(
"There were no changes applied to the role. "
"No edits were taken"
)
return _role
[docs]
class Role(PartialRole):
def __init__(
self,
*,
state: "DiscordAPI",
guild: Union["PartialGuild", "Guild"],
data: dict
):
super().__init__(state=state, id=int(data["id"]), guild_id=guild.id)
self.name: str = data["name"]
self.hoist: bool = data["hoist"]
self.managed: bool = data.get("managed", False)
self.mentionable: bool = data.get("mentionable", False)
self.permissions: Permissions = Permissions(int(data["permissions"]))
self.colour: Colour = Colour(int(data["color"]))
self.position: int = int(data["position"])
self.tags: dict = data.get("tags", {})
self.bot_id: Optional[int] = utils.get_int(data, "bot_id")
self.integration_id: Optional[int] = utils.get_int(data, "integration_id")
self.subscription_listing_id: Optional[int] = utils.get_int(data, "subscription_listing_id")
self.unicode_emoji: str | None = data.get("unicode_emoji", None)
self._premium_subscriber: bool = "premium_subscriber" in self.tags
self._available_for_purchase: bool = "available_for_purchase" in self.tags
self._guild_connections: bool = "guild_connections" in self.tags
self._icon: str | None = data.get("icon", None)
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return f"<Role id={self.id} name='{self.name}'>"
@property
def icon(self) -> Asset | None:
""" `Asset | None`: Returns the icon of the role if it's custom """
if self._icon is None:
return None
return Asset._from_icon(
state=self._state,
object_id=self.id,
icon_hash=self._icon,
path="role"
)
@property
def display_icon(self) -> Asset | str | None:
""" `Asset | str | None`: Returns the display icon of the role """
return self.icon or self.unicode_emoji
[docs]
def is_bot_managed(self) -> bool:
""" `bool`: Returns whether the role is bot managed """
return self.bot_id is not None
[docs]
def is_integration(self) -> bool:
""" `bool`: Returns whether the role is an integration """
return self.integration_id is not None
[docs]
def is_premium_subscriber(self) -> bool:
""" `bool`: Returns whether the role is a premium subscriber """
return self._premium_subscriber
[docs]
def is_available_for_purchase(self) -> bool:
""" `bool`: Returns whether the role is available for purchase """
return self._available_for_purchase
[docs]
def is_guild_connection(self) -> bool:
""" `bool`: Returns whether the role is a guild connection """
return self._guild_connections