Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions hikari/api/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,21 @@ def deserialize_guild_widget(self, payload: data_binding.JSONObject) -> guild_mo
The deserialized guild widget object.
"""

@abc.abstractmethod
def deserialize_guild_widget_settings(self, payload: data_binding.JSONObject) -> guild_models.GuildWidgetSettings:
"""Parse a raw payload from Discord into a guild widget settings object.

Parameters
----------
payload
The JSON payload to deserialize.

Returns
-------
hikari.guilds.GuildWidget
The deserialized guild widget settings object.
"""

@abc.abstractmethod
def deserialize_welcome_screen(self, payload: data_binding.JSONObject) -> guild_models.WelcomeScreen:
"""Parse a raw payload from Discord into a guild welcome screen object.
Expand Down
40 changes: 36 additions & 4 deletions hikari/api/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6865,15 +6865,47 @@ async def fetch_widget(self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuil
"""

@abc.abstractmethod
async def edit_widget(
async def fetch_widget_settings(
self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild]
) -> guilds.GuildWidgetSettings:
"""Fetch a guilds's widget.

Parameters
----------
guild
The guild to fetch the widget from. This can be the object
or the ID of an existing guild.

Returns
-------
hikari.guilds.GuildWidgetSettings
The requested guild widget.

Raises
------
hikari.errors.ForbiddenError
If you are missing the [`hikari.permissions.Permissions.MANAGE_GUILD`][] permission.
hikari.errors.NotFoundError
If the guild is not found.
hikari.errors.UnauthorizedError
If you are unauthorized to make the request (invalid/missing token).
hikari.errors.RateLimitTooLongError
Raised in the event that a rate limit occurs that is
longer than `max_rate_limit` when making a request.
hikari.errors.InternalServerError
If an internal error occurs on Discord while handling the request.
"""

@abc.abstractmethod
async def edit_widget_settings(
self,
guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
*,
channel: undefined.UndefinedNoneOr[snowflakes.SnowflakeishOr[channels_.GuildChannel]] = undefined.UNDEFINED,
enabled: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
) -> guilds.GuildWidget:
"""Fetch a guilds's widget.
) -> guilds.GuildWidgetSettings:
"""Edit a guilds's widget settings.

Parameters
----------
Expand All @@ -6891,7 +6923,7 @@ async def edit_widget(

Returns
-------
hikari.guilds.GuildWidget
hikari.guilds.GuildWidgetSettings
The edited guild widget.

Raises
Expand Down
27 changes: 26 additions & 1 deletion hikari/guilds.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"GuildSystemChannelFlag",
"GuildVerificationLevel",
"GuildWidget",
"GuildWidgetSettings",
"Integration",
"IntegrationAccount",
"IntegrationApplication",
Expand Down Expand Up @@ -313,7 +314,31 @@ class GuildNSFWLevel(int, enums.Enum):
@attrs_extensions.with_copy
@attrs.define(kw_only=True, weakref_slot=False)
class GuildWidget:
"""Represents a guild widget."""
"""Represents a guild widget object."""

id: snowflakes.Snowflake = attrs.field(repr=True)
"""The ID of the guild."""

name: str = attrs.field(repr=True)
"""The name of the guild."""

instant_invite: str | None = attrs.field(repr=True)
"""Instant invite for the guilds specified widget invite channel."""

channels: typing.Sequence[channels_.PartialChannel] = attrs.field(repr=True)
"""The voice and stage channels accessible by the @everyone role."""

members: typing.Sequence[users.PartialUser] = attrs.field(repr=True)
"""The members in the guild."""

presence_count: int
"""The number of online members."""


@attrs_extensions.with_copy
@attrs.define(kw_only=True, weakref_slot=False)
class GuildWidgetSettings:
"""Represents a guild widget settings object."""

app: traits.RESTAware = attrs.field(
repr=False, eq=False, hash=False, metadata={attrs_extensions.SKIP_DEEP_COPY: True}
Expand Down
13 changes: 12 additions & 1 deletion hikari/impl/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1980,11 +1980,22 @@ def deserialize_gateway_bot_info(self, payload: data_binding.JSONObject) -> gate

@typing_extensions.override
def deserialize_guild_widget(self, payload: data_binding.JSONObject) -> guild_models.GuildWidget:
return guild_models.GuildWidget(
id=snowflakes.Snowflake(payload["id"]),
name=payload["name"],
instant_invite=payload.get("instant_invite"),
channels=[self.deserialize_partial_channel(channel) for channel in payload["channels"]],
members=[self.deserialize_user(member) for member in payload["members"]],
presence_count=payload["presence_count"],
)

@typing_extensions.override
def deserialize_guild_widget_settings(self, payload: data_binding.JSONObject) -> guild_models.GuildWidgetSettings:
channel_id: snowflakes.Snowflake | None = None
if (raw_channel_id := payload["channel_id"]) is not None:
channel_id = snowflakes.Snowflake(raw_channel_id)

return guild_models.GuildWidget(app=self._app, channel_id=channel_id, is_enabled=payload["enabled"])
return guild_models.GuildWidgetSettings(app=self._app, channel_id=channel_id, is_enabled=payload["enabled"])

@typing_extensions.override
def deserialize_welcome_screen(self, payload: data_binding.JSONObject) -> guild_models.WelcomeScreen:
Expand Down
17 changes: 13 additions & 4 deletions hikari/impl/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4053,23 +4053,32 @@ async def fetch_widget(self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuil
return self._entity_factory.deserialize_guild_widget(response)

@typing_extensions.override
async def edit_widget(
async def fetch_widget_settings(
self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild]
) -> guilds.GuildWidgetSettings:
route = routes.GET_GUILD_WIDGET_SETTINGS.compile(guild=guild)
response = await self._request(route)
assert isinstance(response, dict)
return self._entity_factory.deserialize_guild_widget_settings(response)

@typing_extensions.override
async def edit_widget_settings(
self,
guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
*,
channel: undefined.UndefinedNoneOr[snowflakes.SnowflakeishOr[channels_.GuildChannel]] = undefined.UNDEFINED,
enabled: undefined.UndefinedOr[bool] = undefined.UNDEFINED,
reason: undefined.UndefinedOr[str] = undefined.UNDEFINED,
) -> guilds.GuildWidget:
route = routes.PATCH_GUILD_WIDGET.compile(guild=guild)
) -> guilds.GuildWidgetSettings:
route = routes.PATCH_GUILD_WIDGET_SETTINGS.compile(guild=guild)

body = data_binding.JSONObjectBuilder()
body.put("enabled", enabled)
body.put_snowflake("channel", channel)

response = await self._request(route, json=body, reason=reason)
assert isinstance(response, dict)
return self._entity_factory.deserialize_guild_widget(response)
return self._entity_factory.deserialize_guild_widget_settings(response)

@typing_extensions.override
async def fetch_welcome_screen(self, guild: snowflakes.SnowflakeishOr[guilds.PartialGuild]) -> guilds.WelcomeScreen:
Expand Down
5 changes: 3 additions & 2 deletions hikari/internal/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,9 @@ def compile_to_file(
POST_GUILD_CHANNELS: typing.Final[Route] = Route(POST, "/guilds/{guild}/channels")
PATCH_GUILD_CHANNELS: typing.Final[Route] = Route(PATCH, "/guilds/{guild}/channels")

GET_GUILD_WIDGET: typing.Final[Route] = Route(GET, "/guilds/{guild}/widget")
PATCH_GUILD_WIDGET: typing.Final[Route] = Route(PATCH, "/guilds/{guild}/widget")
GET_GUILD_WIDGET: typing.Final[Route] = Route(GET, "/guilds/{guild}/widget.json")
GET_GUILD_WIDGET_SETTINGS: typing.Final[Route] = Route(GET, "/guilds/{guild}/widget")
PATCH_GUILD_WIDGET_SETTINGS: typing.Final[Route] = Route(PATCH, "/guilds/{guild}/widget")

GET_GUILD_WELCOME_SCREEN: typing.Final[Route] = Route(GET, "/guilds/{guild}/welcome-screen")
PATCH_GUILD_WELCOME_SCREEN: typing.Final[Route] = Route(PATCH, "/guilds/{guild}/welcome-screen")
Expand Down
49 changes: 40 additions & 9 deletions tests/hikari/impl/test_entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3252,18 +3252,49 @@ def test_deserialize_gateway_bot(self, entity_factory_impl, gateway_bot_payload)
################

@pytest.fixture
def guild_embed_payload(self):
def guild_widget_settings_payload(self):
return {"channel_id": "123123123", "enabled": True}

def test_deserialize_widget_embed(self, entity_factory_impl, mock_app, guild_embed_payload):
guild_embed = entity_factory_impl.deserialize_guild_widget(guild_embed_payload)
assert guild_embed.app is mock_app
assert guild_embed.channel_id == 123123123
assert guild_embed.is_enabled is True
assert isinstance(guild_embed, guild_models.GuildWidget)
def test_deserialize_widget_settings(self, entity_factory_impl, mock_app, guild_widget_settings_payload):
widget_settings = entity_factory_impl.deserialize_guild_widget_settings(guild_widget_settings_payload)
assert widget_settings.app is mock_app
assert widget_settings.channel_id == 123123123
assert widget_settings.is_enabled is True
assert isinstance(widget_settings, guild_models.GuildWidgetSettings)

def test_deserialize_guild_embed_with_null_fields(self, entity_factory_impl, mock_app):
assert entity_factory_impl.deserialize_guild_widget({"channel_id": None, "enabled": True}).channel_id is None
def test_deserialize_widget_settings_with_null_fields(self, entity_factory_impl, mock_app):
assert (
entity_factory_impl.deserialize_guild_widget_settings({"channel_id": None, "enabled": True}).channel_id
is None
)

@pytest.fixture
def guild_widget_payload(self, member_payload):
return {
"id": "123",
"name": "Hikari",
"instant_invite": "hikari",
"channels": [
{"id": "111", "name": "Some stupid voice channel", "position": 2},
{"id": "222", "name": "yeah there is too many of these things", "position": 1},
],
"members": [member_payload],
"presence_count": 1,
}

def test_deserialize_widget(self, entity_factory_impl, guild_widget_payload):
widget = entity_factory_impl.deserialize_guild_widget(guild_widget_payload)
assert widget.id == snowflakes.Snowflake(123)
assert widget.name == "Hikari"
assert widget.instant_invite == "hikari"
assert widget.channels == []
assert widget.members == []
assert widget.presence_count == 1
assert isinstance(widget, guild_models.GuildWidget)

def test_deserialize_widget_with_null_fields(self, entity_factory_impl, guild_widget_payload):
guild_widget_payload["instant_invite"] = None
assert entity_factory_impl.deserialize_guild_widget(guild_widget_payload).instant_invite is None

@pytest.fixture
def guild_welcome_screen_payload(self):
Expand Down
41 changes: 26 additions & 15 deletions tests/hikari/impl/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5911,50 +5911,61 @@ async def test_fetch_widget(self, rest_client):
rest_client._request.assert_awaited_once_with(expected_route)
rest_client._entity_factory.deserialize_guild_widget.assert_called_once_with({"id": "789"})

async def test_edit_widget(self, rest_client):
async def test_fetch_widget_settings(self, rest_client):
widget = StubModel(789)
expected_route = routes.GET_GUILD_WIDGET_SETTINGS.compile(guild=123)
rest_client._request = mock.AsyncMock(return_value={"id": "789"})
rest_client._entity_factory.deserialize_guild_widget_settings = mock.Mock(return_value=widget)

assert await rest_client.fetch_widget_settings(StubModel(123)) == widget

rest_client._request.assert_awaited_once_with(expected_route)
rest_client._entity_factory.deserialize_guild_widget_settings.assert_called_once_with({"id": "789"})

async def test_edit_widget_settings(self, rest_client):
widget = StubModel(456)
expected_route = routes.PATCH_GUILD_WIDGET.compile(guild=123)
expected_route = routes.PATCH_GUILD_WIDGET_SETTINGS.compile(guild=123)
expected_json = {"enabled": True, "channel": "456"}
rest_client._request = mock.AsyncMock(return_value={"id": "456"})
rest_client._entity_factory.deserialize_guild_widget = mock.Mock(return_value=widget)
rest_client._entity_factory.deserialize_guild_widget_settings = mock.Mock(return_value=widget)

returned = await rest_client.edit_widget(
returned = await rest_client.edit_widget_settings(
StubModel(123), channel=StubModel(456), enabled=True, reason="this should have been enabled"
)
assert returned is widget

rest_client._request.assert_awaited_once_with(
expected_route, json=expected_json, reason="this should have been enabled"
)
rest_client._entity_factory.deserialize_guild_widget.assert_called_once_with({"id": "456"})
rest_client._entity_factory.deserialize_guild_widget_settings.assert_called_once_with({"id": "456"})

async def test_edit_widget_when_channel_is_None(self, rest_client):
async def test_edit_widget_settings_when_channel_is_None(self, rest_client):
widget = StubModel(456)
expected_route = routes.PATCH_GUILD_WIDGET.compile(guild=123)
expected_route = routes.PATCH_GUILD_WIDGET_SETTINGS.compile(guild=123)
expected_json = {"enabled": True, "channel": None}
rest_client._request = mock.AsyncMock(return_value={"id": "456"})
rest_client._entity_factory.deserialize_guild_widget = mock.Mock(return_value=widget)
rest_client._entity_factory.deserialize_guild_widget_settings = mock.Mock(return_value=widget)

returned = await rest_client.edit_widget(
returned = await rest_client.edit_widget_settings(
StubModel(123), channel=None, enabled=True, reason="this should have been enabled"
)
assert returned is widget

rest_client._request.assert_awaited_once_with(
expected_route, json=expected_json, reason="this should have been enabled"
)
rest_client._entity_factory.deserialize_guild_widget.assert_called_once_with({"id": "456"})
rest_client._entity_factory.deserialize_guild_widget_settings.assert_called_once_with({"id": "456"})

async def test_edit_widget_without_optionals(self, rest_client):
async def test_edit_widget_settings_without_optionals(self, rest_client):
widget = StubModel(456)
expected_route = routes.PATCH_GUILD_WIDGET.compile(guild=123)
expected_route = routes.PATCH_GUILD_WIDGET_SETTINGS.compile(guild=123)
rest_client._request = mock.AsyncMock(return_value={"id": "456"})
rest_client._entity_factory.deserialize_guild_widget = mock.Mock(return_value=widget)
rest_client._entity_factory.deserialize_guild_widget_settings = mock.Mock(return_value=widget)

assert await rest_client.edit_widget(StubModel(123)) == widget
assert await rest_client.edit_widget_settings(StubModel(123)) == widget

rest_client._request.assert_awaited_once_with(expected_route, json={}, reason=undefined.UNDEFINED)
rest_client._entity_factory.deserialize_guild_widget.assert_called_once_with({"id": "456"})
rest_client._entity_factory.deserialize_guild_widget_settings.assert_called_once_with({"id": "456"})

async def test_fetch_welcome_screen(self, rest_client):
rest_client._request = mock.AsyncMock(return_value={"haha": "funny"})
Expand Down
35 changes: 33 additions & 2 deletions tests/hikari/test_guilds.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ def test_make_icon_url_when_hash_is_not_None(self, model):
)


class TestGuildWidget:
class TestGuildWidgetSettings:
@pytest.fixture
def model(self, mock_app):
return guilds.GuildWidget(app=mock_app, channel_id=snowflakes.Snowflake(420), is_enabled=True)
return guilds.GuildWidgetSettings(app=mock_app, channel_id=snowflakes.Snowflake(420), is_enabled=True)

def test_app_property(self, model, mock_app):
assert model.app is mock_app
Expand All @@ -231,6 +231,37 @@ async def test_fetch_channel_when_None(self, model):
assert await model.fetch_channel() is None


class TestGuildWidget:
@pytest.fixture
def model(self, mock_app):
return guilds.GuildWidget(
id=snowflakes.Snowflake(123),
name="Hikari",
instant_invite="hikari",
channels=[],
members=[],
presence_count=1,
)

def test_id(self, model):
assert model.id == snowflakes.Snowflake(123)

def test_name(self, model):
assert model.name == "Hikari"

def test_instant_invite(self, model):
assert model.instant_invite == "hikari"

def test_channels(self, model):
assert model.channels == []

def test_members(self, model):
assert model.members == []

def test_presence_count(self, model):
assert model.presence_count == 1


class TestMember:
@pytest.fixture
def mock_user(self):
Expand Down
Loading