From 4629c12f6510230e5ca4bb1d1f2ae8cc6fffbbf5 Mon Sep 17 00:00:00 2001 From: MCausc78 Date: Wed, 12 Mar 2025 00:07:07 +0200 Subject: [PATCH 1/3] Initial support for role icons --- pyvolt/http.py | 18 +++++++++++++----- pyvolt/parser.py | 6 +++++- pyvolt/raw/servers.py | 5 ++++- pyvolt/server.py | 29 ++++++++++++++++++++++++----- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/pyvolt/http.py b/pyvolt/http.py index 39d1209..e0574ae 100644 --- a/pyvolt/http.py +++ b/pyvolt/http.py @@ -5500,6 +5500,7 @@ async def edit_role( role: ULIDOr[BaseRole], *, name: UndefinedOr[str] = UNDEFINED, + icon: UndefinedOr[typing.Optional[ResolvableResource]] = UNDEFINED, color: UndefinedOr[typing.Optional[str]] = UNDEFINED, hoist: UndefinedOr[bool] = UNDEFINED, rank: UndefinedOr[int] = UNDEFINED, @@ -5518,6 +5519,8 @@ async def edit_role( The role to edit. name: UndefinedOr[:class:`str`] The new role name. Must be between 1 and 32 characters long. + icon: UndefinedOr[Optional[:class:`.ResolvableResource`]] + The new role icon. color: UndefinedOr[Optional[:class:`str`]] The new role color. Must be a valid CSS color. hoist: UndefinedOr[:class:`bool`] @@ -5551,11 +5554,11 @@ async def edit_role( :class:`NotFound` Possible values for :attr:`~HTTPException.type`: - +--------------+--------------------------------+ - | Value | Reason | - +--------------+--------------------------------+ - | ``NotFound`` | The server/role was not found. | - +--------------+--------------------------------+ + +--------------+-------------------------------------+ + | Value | Reason | + +--------------+-------------------------------------+ + | ``NotFound`` | The server/role/file was not found. | + +--------------+-------------------------------------+ :class:`InternalServerError` Possible values for :attr:`~HTTPException.type`: @@ -5575,6 +5578,11 @@ async def edit_role( if name is not UNDEFINED: payload['name'] = name + if icon is not UNDEFINED: + if icon is None: + remove.append('Icon') + else: + payload['icon'] = await resolve_resource(self.state, icon, tag='icons') if color is not UNDEFINED: if color is None: remove.append('Colour') diff --git a/pyvolt/parser.py b/pyvolt/parser.py index 89fb4ce..d923b9b 100644 --- a/pyvolt/parser.py +++ b/pyvolt/parser.py @@ -3168,16 +3168,18 @@ def parse_role(self, payload: raw.Role, role_id: str, server_id: str, /) -> Role server_id: :class:`str` The server's ID the role belongs to. - Returns ------- :class:`Role` The parsed role object. """ + icon = payload.get('icon') + return Role( state=self.state, id=role_id, name=payload['name'], + internal_icon=None if icon is None else self.parse_asset(icon), permissions=self.parse_permission_override_field(payload['permissions']), color=payload.get('colour'), hoist=payload.get('hoist', False), @@ -3579,6 +3581,7 @@ def parse_server_role_update_event( data = payload['data'] clear = payload['clear'] + icon = data.get('icon') permissions = data.get('permissions') return RawServerRoleUpdateEvent( @@ -3588,6 +3591,7 @@ def parse_server_role_update_event( id=payload['role_id'], server_id=payload['id'], name=data.get('name', UNDEFINED), + internal_icon=None if 'Icon' in clear else UNDEFINED if icon is None else self.parse_asset(icon), permissions=UNDEFINED if permissions is None else self.parse_permission_override_field(permissions), color=None if 'Colour' in clear else data.get('colour', UNDEFINED), hoist=data.get('hoist', UNDEFINED), diff --git a/pyvolt/raw/servers.py b/pyvolt/raw/servers.py index 38f2107..95c1b90 100644 --- a/pyvolt/raw/servers.py +++ b/pyvolt/raw/servers.py @@ -49,6 +49,7 @@ class PartialServer(typing.TypedDict): class Role(typing.TypedDict): name: str + icon: typing_extensions.NotRequired[File] permissions: OverrideField colour: typing_extensions.NotRequired[str] hoist: typing_extensions.NotRequired[bool] @@ -57,6 +58,7 @@ class Role(typing.TypedDict): class PartialRole(typing.TypedDict): name: typing_extensions.NotRequired[str] + icon: typing_extensions.NotRequired[File] permissions: typing_extensions.NotRequired[OverrideField] colour: typing_extensions.NotRequired[str] hoist: typing_extensions.NotRequired[bool] @@ -64,7 +66,7 @@ class PartialRole(typing.TypedDict): FieldsServer = typing.Literal['Description', 'Categories', 'SystemMessages', 'Icon', 'Banner'] -FieldsRole = typing.Literal['Colour'] +FieldsRole = typing.Literal['Colour', 'Icon'] class Category(typing.TypedDict): @@ -128,6 +130,7 @@ class DataEditServer(typing.TypedDict): class DataEditRole(typing.TypedDict): name: typing_extensions.NotRequired[str] + icon: typing_extensions.NotRequired[str] colour: typing_extensions.NotRequired[str] hoist: typing_extensions.NotRequired[bool] rank: typing_extensions.NotRequired[int] diff --git a/pyvolt/server.py b/pyvolt/server.py index bd22281..4050860 100644 --- a/pyvolt/server.py +++ b/pyvolt/server.py @@ -276,6 +276,7 @@ async def edit( self, *, name: UndefinedOr[str] = UNDEFINED, + icon: UndefinedOr[typing.Optional[ResolvableResource]] = UNDEFINED, color: UndefinedOr[typing.Optional[str]] = UNDEFINED, hoist: UndefinedOr[bool] = UNDEFINED, rank: UndefinedOr[int] = UNDEFINED, @@ -290,6 +291,8 @@ async def edit( ---------- name: UndefinedOr[:class:`str`] The new role name. Must be between 1 and 32 characters long. + icon: UndefinedOr[Optional[:class:`.ResolvableResource`]] + The new role icon. color: UndefinedOr[Optional[:class:`str`]] The new role color. Must be a valid CSS color. hoist: UndefinedOr[:class:`bool`] @@ -323,11 +326,11 @@ async def edit( :class:`NotFound` Possible values for :attr:`~HTTPException.type`: - +--------------+--------------------------------+ - | Value | Reason | - +--------------+--------------------------------+ - | ``NotFound`` | The server/role was not found. | - +--------------+--------------------------------+ + +--------------+-------------------------------------+ + | Value | Reason | + +--------------+-------------------------------------+ + | ``NotFound`` | The server/role/file was not found. | + +--------------+-------------------------------------+ :class:`InternalServerError` Possible values for :attr:`~HTTPException.type`: @@ -346,6 +349,7 @@ async def edit( self.server_id, self.id, name=name, + icon=icon, color=color, hoist=hoist, rank=rank, @@ -428,6 +432,9 @@ class PartialRole(BaseRole): name: UndefinedOr[str] = field(repr=True, kw_only=True) """UndefinedOr[:class:`str`]: The new role's name.""" + internal_icon: UndefinedOr[typing.Optional[StatelessAsset]] = field(repr=True, kw_only=True) + """UndefinedOr[Optional[:class:`.StatelessAsset`]]: The new role's icon, if any.""" + permissions: UndefinedOr[PermissionOverride] = field(repr=True, kw_only=True) """UndefinedOr[:class:`.PermissionOverride`]: The new role's permissions.""" @@ -444,6 +451,7 @@ def into_full(self) -> typing.Optional[Role]: """Optional[:class:`.Role`]: Tries transform this partial role into full object. This is useful when caching role.""" if ( self.name is not UNDEFINED + and self.internal_icon is not UNDEFINED and self.permissions is not UNDEFINED and self.hoist is not UNDEFINED and self.rank is not UNDEFINED @@ -454,12 +462,18 @@ def into_full(self) -> typing.Optional[Role]: id=self.id, server_id=self.server_id, name=self.name, + internal_icon=self.internal_icon, permissions=self.permissions, color=color, hoist=self.hoist, rank=self.rank, ) + @property + def icon(self) -> UndefinedOr[typing.Optional[Asset]]: + """UndefinedOr[Optional[:class:`.Asset`]]: The stateful role icon.""" + return self.internal_icon and self.internal_icon.attach_state(self.state, 'icons') + @define(slots=True) class Role(BaseRole): @@ -468,6 +482,9 @@ class Role(BaseRole): name: str = field(repr=True, kw_only=True) """:class:`str`: The role's name.""" + internal_icon: UndefinedOr[typing.Optional[StatelessAsset]] = field(repr=True, kw_only=True) + """UndefinedOr[Optional[:class:`.StatelessAsset`]]: The new server's icon, if any.""" + permissions: PermissionOverride = field(repr=True, kw_only=True) """:class:`.PermissionOverride`: Permissions available to this role.""" @@ -488,6 +505,8 @@ def locally_update(self, data: PartialRole, /) -> None: """ if data.name is not UNDEFINED: self.name = data.name + if data.internal_icon is not UNDEFINED: + self.internal_icon = data.internal_icon if data.permissions is not UNDEFINED: self.permissions = data.permissions if data.color is not UNDEFINED: From 9a2676f8f4abb626157d7ec4f33b91f45aff8519 Mon Sep 17 00:00:00 2001 From: MCausc78 Date: Sat, 22 Mar 2025 00:24:17 +0200 Subject: [PATCH 2/3] Update to_dict for Role --- pyvolt/server.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pyvolt/server.py b/pyvolt/server.py index 0d4ac90..482c9e9 100644 --- a/pyvolt/server.py +++ b/pyvolt/server.py @@ -594,8 +594,8 @@ class Role(BaseRole): name: str = field(repr=True, kw_only=True) """:class:`str`: The role's name.""" - internal_icon: UndefinedOr[typing.Optional[StatelessAsset]] = field(repr=True, kw_only=True) - """UndefinedOr[Optional[:class:`.StatelessAsset`]]: The new server's icon, if any.""" + internal_icon: typing.Optional[StatelessAsset] = field(repr=True, kw_only=True) + """Optional[:class:`.StatelessAsset`]: The new server's icon, if any.""" permissions: PermissionOverride = field(repr=True, kw_only=True) """:class:`.PermissionOverride`: Permissions available to this role.""" @@ -631,17 +631,16 @@ def locally_update(self, data: PartialRole, /) -> None: def to_dict(self) -> raw.Role: """:class:`dict`: Convert role to raw data.""" - if self.color is None: - payload = { - 'name': self.name, - 'permissions': self.permissions.to_field_dict(), - } - else: - payload = { - 'name': self.name, - 'permissions': self.permissions.to_field_dict(), - 'colour': self.color, - } + payload: dict[str, typing.Any] = { + 'name': self.name, + } + if self.internal_icon is not None: + payload['icon'] = self.internal_icon.to_dict('icons') + + payload['permissions'] = self.permissions.to_field_dict() + + if self.color is not None: + payload['colour'] = self.color if self.hoist: payload['hoist'] = self.hoist From fc2ea24ffab3bd3600423fd26ac706995a00db19 Mon Sep 17 00:00:00 2001 From: MCausc78 Date: Thu, 1 May 2025 02:42:28 +0300 Subject: [PATCH 3/3] Add missing versionadded for Role.icon --- pyvolt/http.py | 2 ++ pyvolt/server.py | 25 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pyvolt/http.py b/pyvolt/http.py index 7035022..4cd0b0c 100644 --- a/pyvolt/http.py +++ b/pyvolt/http.py @@ -6194,6 +6194,8 @@ async def edit_role( The new role name. Must be between 1 and 32 characters long. icon: UndefinedOr[Optional[:class:`.ResolvableResource`]] The new role icon. + + .. versionadded:: 1.2 color: UndefinedOr[Optional[:class:`str`]] The new role color. Must be a valid CSS color. hoist: UndefinedOr[:class:`bool`] diff --git a/pyvolt/server.py b/pyvolt/server.py index 6e4d6dd..30dac68 100644 --- a/pyvolt/server.py +++ b/pyvolt/server.py @@ -405,6 +405,8 @@ async def edit( The new role name. Must be between 1 and 32 characters long. icon: UndefinedOr[Optional[:class:`.ResolvableResource`]] The new role icon. + + .. versionadded:: 1.2 color: UndefinedOr[Optional[:class:`str`]] The new role color. Must be a valid CSS color. hoist: UndefinedOr[:class:`bool`] @@ -555,7 +557,10 @@ class PartialRole(BaseRole): """UndefinedOr[:class:`str`]: The new role's name.""" internal_icon: UndefinedOr[typing.Optional[StatelessAsset]] = field(repr=True, kw_only=True) - """UndefinedOr[Optional[:class:`.StatelessAsset`]]: The new role's icon, if any.""" + """UndefinedOr[Optional[:class:`.StatelessAsset`]]: The new role's icon, if any. + + .. versionadded:: 1.2 + """ permissions: UndefinedOr[PermissionOverride] = field(repr=True, kw_only=True) """UndefinedOr[:class:`.PermissionOverride`]: The new role's permissions.""" @@ -593,7 +598,10 @@ def into_full(self) -> typing.Optional[Role]: @property def icon(self) -> UndefinedOr[typing.Optional[Asset]]: - """UndefinedOr[Optional[:class:`.Asset`]]: The stateful role icon.""" + """UndefinedOr[Optional[:class:`.Asset`]]: The stateful role icon. + + .. versionadded:: 1.2 + """ return self.internal_icon and self.internal_icon.attach_state(self.state, 'icons') @@ -608,7 +616,10 @@ class Role(BaseRole): """:class:`str`: The role's name.""" internal_icon: typing.Optional[StatelessAsset] = field(repr=True, kw_only=True) - """Optional[:class:`.StatelessAsset`]: The new server's icon, if any.""" + """Optional[:class:`.StatelessAsset`]: The new server's icon, if any. + + .. versionadded:: 1.2 + """ permissions: PermissionOverride = field(repr=True, kw_only=True) """:class:`.PermissionOverride`: Permissions available to this role.""" @@ -641,6 +652,14 @@ def locally_update(self, data: PartialRole, /) -> None: if data.rank is not UNDEFINED: self.rank = data.rank + @property + def icon(self) -> UndefinedOr[typing.Optional[Asset]]: + """UndefinedOr[Optional[:class:`.Asset`]]: The stateful role icon. + + .. versionadded:: 1.2 + """ + return self.internal_icon and self.internal_icon.attach_state(self.state, 'icons') + def to_dict(self) -> raw.Role: """:class:`dict`: Convert role to raw data."""