add flags

This commit is contained in:
Zomatree
2021-11-20 22:33:10 +00:00
parent a16bac6207
commit 39098692bc
8 changed files with 269 additions and 292 deletions
+6
View File
@@ -139,6 +139,12 @@ Status
.. autonamedtuple:: Status
UserBadges
~~~~~~~~~~~
.. autoclass:: UserBadges
:members:
Enums
======
+1
View File
@@ -8,6 +8,7 @@ from .enums import (AssetType, ChannelType, PresenceType, RelationshipType,
SortType)
from .errors import HTTPError, RevoltError, ServerError
from .file import File
from .flags import UserBadges
from .member import Member
from .message import Masquerade, Message, MessageReply
from .messageable import Messageable
+8 -8
View File
@@ -69,12 +69,12 @@ class GroupDMChannel(Channel, Messageable):
self.owner = state.get_user(data["owner"])
if perms := data.get("permissions"):
self.permissions = ChannelPermissions(perms)
self.permissions = ChannelPermissions._from_value(perms)
def _update(self, *, name: Optional[str] = None, recipients: Optional[list[str]] = None):
if name:
self.name = name
if recipients:
self.recipients = [self.state.get_user(user_id) for user_id in recipients]
@@ -93,19 +93,19 @@ class TextChannel(Channel, Messageable):
"""A text channel"""
def __init__(self, data: TextChannelPayload, state: State):
super().__init__(data, state)
self.server_id = data["server"]
self.name = data["name"]
self.description = data.get("description")
last_message_id = data.get("last_message")
self.last_message_id = last_message_id
if perms := data.get("default_permissions"):
self.default_permissions = ChannelPermissions(perms)
self.default_permissions = ChannelPermissions._from_value(perms)
if role_perms := data.get("role_permissions"):
self.role_permissions = {role_id: ChannelPermissions(perms) for role_id, perms in role_perms.items()}
self.role_permissions = {role_id: ChannelPermissions._from_value(perms) for role_id, perms in role_perms.items()}
def _get_channel_id(self) -> str:
return self.id
@@ -149,10 +149,10 @@ class VoiceChannel(Channel):
self.description = data.get("description")
if perms := data.get("default_permissions"):
self.default_permissions = ChannelPermissions(perms)
self.default_permissions = ChannelPermissions._from_value(perms)
if role_perms := data.get("role_permissions"):
self.role_permissions = {role_id: ChannelPermissions(perms) for role_id, perms in role_perms.items()}
self.role_permissions = {role_id: ChannelPermissions._from_value(perms) for role_id, perms in role_perms.items()}
def _update(self, *, name: Optional[str] = None, description: Optional[str] = None):
if name:
+146
View File
@@ -0,0 +1,146 @@
from __future__ import annotations
from typing import Callable, Iterator, Optional, TypeVar, Union, overload
__all__ = ("flag_value", "Flags", "UserBadges")
F_T = TypeVar("F_T", bound="Flags")
F_V = TypeVar("F_V", bound="flag_value")
class flag_value:
__slots__ = ("flag", "__doc__")
def __init__(self, func: Callable[[], int]):
self.flag = func()
self.__doc__ = func.__doc__
@overload
def __get__(self: F_V, instance: None, owner: type[F_T]) -> F_V:
...
@overload
def __get__(self, instance: F_T, owner: type[F_T]) -> bool:
...
def __get__(self: F_V, instance: Optional[F_T], owner: type[F_T]) -> Union[F_V, bool]:
if instance is None:
return self
return instance._check_flag(self.flag)
def __set__(self, instance: Flags, value: bool):
instance._set_flag(self.flag, value)
class Flags:
def __init__(self, **kwargs: bool):
self.value = 0
for k, v in kwargs.items():
setattr(self, k, v)
@classmethod
def _from_value(cls: type[F_T], value: int) -> F_T:
self = cls.__new__(cls)
self.value = value
return self
def _check_flag(self, flag: int) -> bool:
return (self.value & flag) == flag
def _set_flag(self, flag: int, value: bool):
if value:
self.value |= flag
else:
self.value &= ~flag
def __eq__(self: F_T, other: F_T) -> bool:
return self.value == other.value
def __ne__(self: F_T, other: F_T) -> bool:
return not self.__eq__(other)
def __or__(self: F_T, other: F_T) -> F_T:
return self.__class__._from_value(self.value | other.value)
def __and__(self: F_T, other: F_T) -> F_T:
return self.__class__._from_value(self.value & other.value)
def __invert__(self: F_T) -> F_T:
return self.__class__._from_value(~self.value)
def __add__(self: F_T, other: F_T) -> F_T:
return self | other
def __sub__(self: F_T, other: F_T) -> F_T:
return self & ~other
def __lt__(self: F_T, other: F_T) -> bool:
return self.value < other.value
def __gt__(self: F_T, other: F_T) -> bool:
return self.value > other.value
def __repr__(self):
return f"<{self.__class__.__name__} value={self.value}>"
def __iter__(self) -> Iterator[tuple[str, bool]]:
for name, value in self.__class__.__dict__.items():
if isinstance(value, flag_value):
yield name, value.__get__(self, self.__class__)
def __hash__(self) -> int:
return hash(self.value)
class UserBadges(Flags):
"""Contains all user badges"""
@flag_value
def developer():
""":class:`bool` The developer badge."""
return 1 << 0
@flag_value
def translator():
""":class:`bool` The translator badge."""
return 1 << 1
@flag_value
def supporter():
""":class:`bool` The supporter badge."""
return 1 << 2
@flag_value
def responsible_disclosure():
""":class:`bool` The responsible disclosure badge."""
return 1 << 3
@flag_value
def founder():
""":class:`bool` The founder badge."""
return 1 << 4
@flag_value
def platform_moderation():
""":class:`bool` The platform moderation badge."""
return 1 << 5
@flag_value
def active_supporter():
""":class:`bool` The active supporter badge."""
return 1 << 6
@flag_value
def paw():
""":class:`bool` The paw badge."""
return 1 << 7
@flag_value
def early_adopter():
""":class:`bool` The early adopter badge."""
return 1 << 8
@flag_value
def reserved_relevant_joke_badge_1():
""":class:`bool` The reserved relevant joke badge 1 badge."""
return 1 << 9
+66 -264
View File
@@ -1,15 +1,12 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .types import Permission as PermissionTuple
from .flags import Flags, flag_value
__all__ = (
"ChannelPermissions",
"ServerPermissions"
)
)
# Channel permissions
#
@@ -37,315 +34,120 @@ __all__ = (
# ChangeAvatar = 0b00000000000000000100000000000000 // 16382
# RemoveAvatars = 0b00000000000000001000000000000000 // 32768
class _Permission:
__slots__ = ("_value",)
def __init__(self, value: int):
self._value = value
def __or__(self, p: _Permission):
self._value |= p._value
return self
def __and__(self, p: _Permission):
self._value &= p._value
return self
def __invert__(self):
self._value = ~self._value
return self
def __add__(self, p: _Permission):
# For example we have a bit array `0011` representing permissions
# To allow a permission we would convert the bit for that permission from 0 to 1
# Let's say we want to allow permissions with bit array `0101`
# Our desired result is `0111` which can be achieved by simple OR (A+B)
return self | p
def __sub__(self, p: _Permission):
# For example we have a bit array `0011` representing permissions
# To deny a permission we would convert the bit for that permission from 1 to 0
# Let's say we want to deny permissions with bit array `0101`
# Our desired result is `0010` which can be achieved by Selective Clear Algorithm (A.B')
return self & ~p
def __lt__(self, p: _Permission):
return self._value < p._value
def __gt__(self, p: _Permission):
return self._value > p._value
def __eq__(self, p: _Permission):
return self._value == p._value
def __repr__(self):
return f"{self.__class__.__name__}<{self._value:032b}>"
def _check(self, bit_pos: int):
# To check if a permission is present we would need to compare the bit for that position in our bit array
# For example if we have permission `0100` and we need to check if it is present in our bit array `0101`
# We need to compare the 3rd bit from the left, so we shift it 2 places (n-1) and perform AND with 1
# If the result is 1 the bit was 1 and the permission is present
return (self._value >> bit_pos) & 1 == 1
class ChannelPermissions(_Permission):
class ChannelPermissions(Flags):
"""Represents the channel permissions for a role as seen in channel settings."""
__slots__ = ()
@property
def value(self) -> int:
return self._value
@classmethod
def none(cls) -> ChannelPermissions:
return cls(0)
return cls._from_value(0)
@classmethod
def all(cls) -> ChannelPermissions:
# channel permissions here do not have manage messages permission
return cls(0b00000000000000000000000011111011)
return cls._from_value(0b11111011)
@classmethod
def view(cls) -> ChannelPermissions:
return cls(0b00000000000000000000000000000001)
# view permission is always present and therefore is included in every permission below
return cls._from_value(0b1)
@classmethod
def send_message(cls) -> ChannelPermissions:
return cls(0b00000000000000000000000000000011)
return cls._from_value(0b11)
@classmethod
def manage_channel(cls) -> ChannelPermissions:
return cls(0b00000000000000000000000000001001)
return cls._from_value(0b1001)
@classmethod
def voice_call(cls) -> ChannelPermissions:
return cls(0b00000000000000000000000000010001)
return cls._from_value(0b10001)
@classmethod
def invite_others(cls) -> ChannelPermissions:
return cls(0b00000000000000000000000000100001)
return cls._from_value(0b100001)
@classmethod
def embed_links(cls) -> ChannelPermissions:
return cls(0b00000000000000000000000001000001)
return cls._from_value(0b1000001)
@classmethod
def upload_files(cls) -> ChannelPermissions:
return cls(0b00000000000000000000000010000001)
return cls._from_value(0b10000001)
@property
def can_view(self) -> bool:
return self._check(0)
@flag_value
def can_view() -> int:
return 1 << 0
@property
def can_send_message(self) -> bool:
return self._check(1)
@flag_value
def can_send_message() -> int:
return 1 << 1
@property
def can_manage_channel(self) -> bool:
return self._check(3)
@flag_value
def can_manage_channel() -> int:
return 1 << 3
@property
def can_voice_call(self) -> bool:
return self._check(4)
@flag_value
def can_voice_call() -> int:
return 1 << 4
@property
def can_invite_others(self) -> bool:
return self._check(5)
@flag_value
def can_invite_others() -> int:
return 1 << 5
@property
def can_embed_links(self) -> bool:
return self._check(6)
@flag_value
def can_embed_links() -> int:
return 1 << 6
@property
def can_upload_files(self) -> bool:
return self._check(7)
@flag_value
def can_upload_files() -> int:
return 1 << 7
class ServerPermissions:
class ServerPermissions(Flags):
"""Represents the server permissions for a role as seen in server settings."""
__slots__ = ("_server_perms", "_channel_perms", )
def __init__(self, server_perms: int, channel_perms: int) -> None:
self._server_perms = _Permission(server_perms)
self._channel_perms = _Permission(channel_perms)
def __add__(self, p: ServerPermissions) -> ServerPermissions:
self._server_perms += p._server_perms
self._channel_perms += p._channel_perms
return self
def __sub__(self, p: ServerPermissions) -> ServerPermissions:
self._server_perms -= p._server_perms
self._channel_perms -= p._channel_perms
return self
def __lt__(self, p: ServerPermissions) -> bool:
return self._server_perms < p._server_perms and self._channel_perms < p._channel_perms
def __gt__(self, p: ServerPermissions) -> bool:
return self._server_perms > p._server_perms and self._channel_perms > p._channel_perms
def __eq__(self, p: ServerPermissions) -> bool:
return self._server_perms == p._server_perms and self._channel_perms == p._channel_perms
def __repr__(self) -> str:
return f"{self.__class__.__name__}<Server: {self._server_perms._value:032b} Channel: {self._channel_perms._value:032b}>"
@property
def value(self) -> PermissionTuple:
return self._server_perms._value, self._channel_perms._value
@classmethod
def none(cls) -> ServerPermissions:
return cls(0, 0)
return cls._from_value(0)
@classmethod
def all(cls) -> ServerPermissions:
# channel permissions here do not have manage channel permission
return cls(0b00000000000000001111000000111111, 0b00000000000000000000000011110111)
return cls._from_value(0b1111000000111111)
# view server and view channel are always present and therefore are included in every permission below
# server permissions
@flag_value
def view_server() -> int:
return 1 << 0
@classmethod
def view_server(cls) -> ServerPermissions:
return cls(0b00000000000000000000000000000001, 1)
@flag_value
def manage_roles() -> int:
return 1 << 1
@classmethod
def manage_roles(cls) -> ServerPermissions:
return cls(0b00000000000000000000000000000011, 1)
@flag_value
def manage_channels() -> int:
return 1 << 2
@classmethod
def manage_channels(cls) -> ServerPermissions:
return cls(0b00000000000000000000000000000101, 1)
@flag_value
def manage_server() -> int:
return 1 << 3
@classmethod
def manage_server(cls) -> ServerPermissions:
return cls(0b00000000000000000000000000001001, 1)
@flag_value
def kick_members() -> int:
return 1 << 4
@classmethod
def kick_members(cls) -> ServerPermissions:
return cls(0b00000000000000000000000000010001, 1)
@flag_value
def ban_members() -> int:
return 1 << 5
@classmethod
def ban_members(cls) -> ServerPermissions:
return cls(0b00000000000000000000000000100001, 1)
@flag_value
def change_nicknames() -> int:
return 1 << 12
@classmethod
def change_nicknames(cls) -> ServerPermissions:
return cls(0b00000000000000000001000000000001, 1)
@flag_value
def manage_nicknames() -> int:
return 1 << 13
@classmethod
def manage_nicknames(cls) -> ServerPermissions:
return cls(0b00000000000000000010000000000001, 1)
@flag_value
def change_avatar() -> int:
return 1 << 14
@classmethod
def change_avatar(cls) -> ServerPermissions:
return cls(0b00000000000000000100000000000001, 1)
@classmethod
def remove_avatars(cls) -> ServerPermissions:
return cls(0b00000000000000001000000000000001, 1)
@property
def can_view_server(self) -> bool:
return self._server_perms._check(0)
@property
def can_manage_roles(self) -> bool:
return self._server_perms._check(1)
@property
def can_manage_channels(self) -> bool:
return self._server_perms._check(2)
@property
def can_manage_server(self) -> bool:
return self._server_perms._check(3)
@property
def can_kick_members(self) -> bool:
return self._server_perms._check(4)
@property
def can_ban_members(self) -> bool:
return self._server_perms._check(5)
@property
def can_change_nicknames(self) -> bool:
return self._server_perms._check(12)
@property
def can_manage_nicknames(self) -> bool:
return self._server_perms._check(13)
@property
def can_change_avatar(self) -> bool:
return self._server_perms._check(14)
@property
def can_remove_avatars(self) -> bool:
return self._server_perms._check(15)
# channel permissions
@classmethod
def view_channels(cls) -> ServerPermissions:
return cls(1, 0b00000000000000000000000000000001)
@classmethod
def send_message(cls) -> ServerPermissions:
return cls(1, 0b00000000000000000000000000000011)
@classmethod
def manage_messages(cls) -> ServerPermissions:
return cls(1, 0b00000000000000000000000000000101)
@classmethod
def voice_call(cls) -> ServerPermissions:
return cls(1, 0b00000000000000000000000000010001)
@classmethod
def invite_others(cls) -> ServerPermissions:
return cls(1, 0b00000000000000000000000000100001)
@classmethod
def embed_links(cls) -> ServerPermissions:
return cls(1, 0b00000000000000000000000001000001)
@classmethod
def upload_files(cls) -> ServerPermissions:
return cls(1, 0b00000000000000000000000010000001)
@property
def can_view_channel(self) -> bool:
return self._channel_perms._check(0)
@property
def can_send_message(self) -> bool:
return self._channel_perms._check(1)
@property
def can_manage_messages(self) -> bool:
return self._channel_perms._check(2)
@property
def can_voice_call(self) -> bool:
return self._channel_perms._check(4)
@property
def can_invite_others(self) -> bool:
return self._channel_perms._check(5)
@property
def can_embed_links(self) -> bool:
return self._channel_perms._check(6)
@property
def can_upload_files(self) -> bool:
return self._channel_perms._check(7)
@flag_value
def remove_avatars() -> int:
return 1 << 15
+24 -10
View File
@@ -2,7 +2,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from .permissions import ServerPermissions
from revolt.types import server
from .permissions import ChannelPermissions, ServerPermissions
if TYPE_CHECKING:
from .server import Server
@@ -14,7 +16,7 @@ __all__ = ("Role",)
class Role:
"""Represents a role
Attributes
-----------
id: :class:`str`
@@ -29,11 +31,13 @@ class Role:
The position of the role in the role heirarchy
server: :class:`Server`
The server the role belongs to
permissions: :class:`ServerPermissions`
server_permissions: :class:`ServerPermissions`
The server permissions for the role
channel_permissions: :class:`ChannelPermissions`
The channel permissions for the role
"""
__slots__ = ("id", "name", "colour", "hoist", "rank", "state", "server", "permissions")
__slots__ = ("id", "name", "colour", "hoist", "rank", "state", "server", "server_permissions", "channel_permissions")
def __init__(self, data: RolePayload, role_id: str, state: State, server: Server):
self.state = state
self.id = role_id
@@ -42,20 +46,30 @@ class Role:
self.hoist = data.get("hoist", False)
self.rank = data.get("rank", 0)
self.server = server
self.permissions = ServerPermissions(*data.get("permissions"))
self.server_permissions = ServerPermissions._from_value(data["permissions"][0])
self.channel_permissions = ChannelPermissions._from_value(data["permissions"][1])
@property
def color(self):
return self.colour
async def set_permissions(self, permissions: ServerPermissions) -> None:
async def set_permissions(self, *, server_permissions: Optional[ServerPermissions] = None, channel_permissions: Optional[ChannelPermissions] = None) -> None:
"""Sets the permissions for a role in a server.
Parameters
-----------
permissions: :class:`ServerPermissions`
The new permissions for the role
server_permissions: Optional[:class:`ServerPermissions`]
The new server permissions for the role
channel_permissions: Optional[:class:`ChannelPermissions`]
The new channel permissions for the role
"""
await self.state.http.set_role_permissions(self.server.id, self.id, *permissions.value)
if not server_permissions and not channel_permissions:
return
server_value = (server_permissions or self.server_permissions).value
channel_value = (channel_permissions or self.channel_permissions).value
await self.state.http.set_role_permissions(self.server.id, self.id, server_value, channel_value)
def _update(self, *, name: Optional[str] = None, colour: Optional[str] = None, hoist: Optional[bool] = None, rank: Optional[int] = None):
if name:
+15 -8
View File
@@ -1,11 +1,11 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union, cast
from typing import TYPE_CHECKING, Optional
from .asset import Asset
from .category import Category
from .channel import Channel
from .permissions import ServerPermissions
from .permissions import ChannelPermissions, ServerPermissions
from .role import Role
if TYPE_CHECKING:
@@ -89,14 +89,15 @@ class Server:
banner: Optional[:class:`Asset`]
The servers banner
"""
__slots__ = ("state", "id", "name", "owner_id", "default_permissions", "_members", "_roles", "_channels", "description", "icon", "banner", "nsfw", "system_messages", "categories")
__slots__ = ("state", "id", "name", "owner_id", "default_server_permissions", "default_channel_permissions", "_members", "_roles", "_channels", "description", "icon", "banner", "nsfw", "system_messages", "categories")
def __init__(self, data: ServerPayload, state: State):
self.state = state
self.id = data["_id"]
self.name = data["name"]
self.owner_id = data["owner"]
self.default_permissions = ServerPermissions(*data["default_permissions"])
self.default_server_permissions = ServerPermissions._from_value(data["default_permissions"][0])
self.default_channel_permissions = ChannelPermissions._from_value(data["default_permissions"][1])
self.description = data.get("description") or None
self.nsfw = data.get("nsfw", False)
self.system_messages = SystemMessages(data.get("system_messages", {}), state)
@@ -130,7 +131,8 @@ class Server:
if banner:
self.banner = Asset(banner, self.state)
if default_permissions:
self.default_permissions = ServerPermissions(*default_permissions)
self.default_server_permissions = ServerPermissions._from_value(default_permissions[0])
self.default_channel_permissions = ChannelPermissions._from_value(default_permissions[1])
if nsfw is not None:
self.nsfw = nsfw
if system_messages is not None:
@@ -203,11 +205,16 @@ class Server:
""":class:`Member` The owner of the server"""
return self.get_member(self.owner_id)
async def set_default_permissions(self, permissions: ServerPermissions) -> None:
async def set_default_permissions(self, *, server_permissions: Optional[ServerPermissions] = None, channel_permissions: Optional[ChannelPermissions] = None) -> None:
"""Sets the default server permissions.
Parameters
-----------
permissions: :class:`ServerPermissions`
server_permissions: Optional[:class:`ServerPermissions`]
The new default server permissions
channel_permissions: Optional[:class:`ChannelPermissions`]
the new default channel permissions
"""
await self.state.http.set_default_permissions(self.id, *permissions.value)
server_value = (server_permissions or self.default_server_permissions).value
channel_value = (channel_permissions or self.default_channel_permissions).value
await self.state.http.set_default_permissions(self.id, server_value, channel_value)
+3 -2
View File
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, NamedTuple, Optional, Union
from .asset import Asset, PartialAsset
from .enums import PresenceType, RelationshipType
from .flags import UserBadges
if TYPE_CHECKING:
from .state import State
@@ -40,7 +41,7 @@ class User:
Whether or not the user is a bot
owner: Optional[:class:`User`]
The bot's owner if the user is a bot
badges: :class:`int`
badges: :class:`UserBadges`
The users badges
online: :class:`bool`
Whether or not the user is online
@@ -69,7 +70,7 @@ class User:
self.bot = False
self.owner_id = None
self.badges = data.get("badges", 0)
self.badges = UserBadges._from_value(data.get("badges", 0))
self.online = data.get("online", False)
self.flags = data.get("flags", 0)