From cb9f9f517ac65611561c70edbfe612dbcdbd1f2b Mon Sep 17 00:00:00 2001 From: Ashwin Vinod Date: Fri, 9 Jul 2021 21:48:01 +0530 Subject: [PATCH 01/30] Implement Stage instances add stubs fix linting make names more consistent. started adding tests change stage_id to id rename stage_instance_events to stage_events. add helpful functions and modify abc thing rename and fix linting fix bugs + add tests for event manager add tests for stage events and fix some bugs add tests for stage_instance fix linting add tests and fix small bugs --- hikari/__init__.py | 1 + hikari/__init__.pyi | 1 + hikari/api/entity_factory.py | 20 +++ hikari/api/event_factory.py | 62 ++++++++ hikari/api/rest.py | 182 ++++++++++++++++++++++ hikari/audit_logs.py | 3 + hikari/events/__init__.py | 1 + hikari/events/__init__.pyi | 1 + hikari/events/stage_events.py | 137 +++++++++++++++++ hikari/impl/entity_factory.py | 12 ++ hikari/impl/event_factory.py | 26 ++++ hikari/impl/event_manager.py | 15 ++ hikari/impl/rest.py | 51 +++++++ hikari/internal/routes.py | 5 + hikari/stage_instances.py | 183 +++++++++++++++++++++++ tests/hikari/events/test_stage_events.py | 72 +++++++++ tests/hikari/impl/test_entity_factory.py | 26 ++++ tests/hikari/impl/test_event_factory.py | 56 +++++++ tests/hikari/impl/test_event_manager.py | 54 +++++++ tests/hikari/impl/test_rest.py | 74 +++++++++ tests/hikari/test_stage_instances.py | 85 +++++++++++ 21 files changed, 1067 insertions(+) create mode 100644 hikari/events/stage_events.py create mode 100644 hikari/stage_instances.py create mode 100644 tests/hikari/events/test_stage_events.py create mode 100644 tests/hikari/test_stage_instances.py diff --git a/hikari/__init__.py b/hikari/__init__.py index c4ae3280e5..3a09bcf4bf 100644 --- a/hikari/__init__.py +++ b/hikari/__init__.py @@ -124,6 +124,7 @@ from hikari.snowflakes import SnowflakeishOr from hikari.snowflakes import SnowflakeishSequence from hikari.snowflakes import Unique +from hikari.stage_instances import * from hikari.stickers import * from hikari.templates import * from hikari.traits import * diff --git a/hikari/__init__.pyi b/hikari/__init__.pyi index 1e8c845337..0703574d26 100644 --- a/hikari/__init__.pyi +++ b/hikari/__init__.pyi @@ -98,6 +98,7 @@ from hikari.snowflakes import Snowflakeish as Snowflakeish from hikari.snowflakes import SnowflakeishOr as SnowflakeishOr from hikari.snowflakes import SnowflakeishSequence as SnowflakeishSequence from hikari.snowflakes import Unique as Unique +from hikari.stage_instances import * from hikari.stickers import * from hikari.templates import * from hikari.traits import * diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index a6ff73c438..230151760e 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -45,6 +45,7 @@ from hikari import scheduled_events as scheduled_events_models from hikari import sessions as gateway_models from hikari import snowflakes + from hikari import stage_instances from hikari import stickers as sticker_models from hikari import templates as template_models from hikari import users as user_models @@ -1994,3 +1995,22 @@ def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_model hikari.errors.UnrecognisedEntityError If the channel type is unknown. """ + + ######################### + # Stage instance models # + ######################### + + @abc.abstractmethod + def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_instances.StageInstance: + """Parse a raw payload from Discord into a guild stage instance object. + + Parameters + ---------- + payload : hikari.internal.data_binding.JSONObject + The JSON payload to deserialize. + + Returns + ------- + hikari.channels.StageInstance + The deserialized stage instance object + """ diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py index 32515c636f..608e8891de 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -52,6 +52,7 @@ from hikari.events import role_events from hikari.events import scheduled_events from hikari.events import shard_events + from hikari.events import stage_events from hikari.events import typing_events from hikari.events import user_events from hikari.events import voice_events @@ -1394,3 +1395,64 @@ def deserialize_voice_server_update_event( hikari.events.voice_events.VoiceServerUpdateEvent The parsed voice server update event object. """ + + ######################### + # STAGE INSTANCE EVENTS # + ######################### + + @abc.abstractmethod + def deserialize_stage_instance_create_event( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> stage_events.StageInstanceCreateEvent: + """Parse a raw payload from Discord into a Stage instance create event object. + + Parameters + ---------- + shard : hikari.api.shard.GatewayShard + The shard that emitted this event. + payload : hikari.internal.data_binding.JSONObject + The dict payload to parse. + + Returns + ------- + hikari.events.voice_events.StageInstanceCreateEvent + The parsed Stage instance create event object. + """ + + @abc.abstractmethod + def deserialize_stage_instance_edit_event( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> stage_events.StageInstanceEditEvent: + """Parse a raw payload from Discord into a Stage instance update event object. + + Parameters + ---------- + shard : hikari.api.shard.GatewayShard + The shard that emitted this event. + payload : hikari.internal.data_binding.JSONObject + The dict payload to parse. + + Returns + ------- + hikari.events.voice_events.StageInstanceEditEvent + The parsed Stage instance update event object. + """ + + @abc.abstractmethod + def deserialize_stage_instance_delete_event( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> stage_events.StageInstanceDeleteEvent: + """Parse a raw payload from Discord into a Stage instance delete event object. + + Parameters + ---------- + shard : hikari.api.shard.GatewayShard + The shard that emitted this event. + payload : hikari.internal.data_binding.JSONObject + The dict payload to parse. + + Returns + ------- + hikari.events.voice_events.StageInstanceDeleteEvent + The parsed Stage instance delete event object. + """ diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 2675297887..7f6a040c28 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -50,6 +50,7 @@ from hikari import permissions as permissions_ from hikari import sessions from hikari import snowflakes + from hikari import stage_instances from hikari import stickers as stickers_ from hikari import templates from hikari import users @@ -8218,3 +8219,184 @@ def fetch_scheduled_event_users( hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. """ + + async def fetch_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + ) -> stage_instances.StageInstance: + """Fetch the Stage instance associated with a guild stage channel. + + Parameters + ---------- + channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + The guild stage channel to fetch the Stage instance from. + + Returns + ------- + typing.Optional[hikari.stage_instances.StageInstance] + The Stage instance associated with the guild stage channel. + + `builtins.None` if no Stage instance exists in the stage channel. + + Raises + ------ + hikari.errors.UnauthorizedError + If you are unauthorized to make the request (invalid/missing token). + hikari.errors.NotFoundError + If the interaction or response is not found. + 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.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + """ + + @abc.abstractmethod + async def create_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + *, + topic: str, + privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, + ) -> stage_instances.StageInstance: + """Create a Stage instance in guild stage channel. + + Parameters + ---------- + channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + The channel to use for the Stage instance creation. + + Other Parameters + ---------------- + topic: builtins.str + The topic for the Stage instance. + + privacy_level: hikari.undefined.UndefinedOr[hikari.stage_instances.StagePrivacyLevel] + The privacy level of the Stage Instance. + + This will be set to `hikari.stage_instances.StagePrivacyLevel.GUILD_ONLY` if not provided. + + Returns + ------- + hikari.stage_instances.StageInstance + The created Stage instance. + + Raises + ------ + hikari.errors.UnauthorizedError + If you are unauthorized to make the request (invalid/missing token). + hikari.errors.NotFoundError + If the interaction or response is not found. + 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.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + """ + + @abc.abstractmethod + async def edit_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + *, + topic: undefined.UndefinedOr[str] = undefined.UNDEFINED, + privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, + ) -> stage_instances.StageInstance: + """Edit the Stage instance in a guild stage channel. + + !!! note + This will raise `hikari.errors.UnauthorizedError` if the bot is not a moderator + of the Stage instance. + + Parameters + ---------- + channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + The channel that the Stage instance is associated with. + + Other Parameters + ---------------- + topic: hikari.undefined.UndefinedOr[builtins.str] + The topic for the Stage instance. + + privacy_level: hikari.undefined.UndefinedOr[hikari.stage_instances.StagePrivacyLevel] + The privacy level of the Stage Instance. + + Returns + ------- + hikari.stage_instances.StageInstance + The edited Stage instance. + + Raises + ------ + hikari.errors.UnauthorizedError + If you are unauthorized to make the request (invalid/missing token). + hikari.errors.NotFoundError + If the interaction or response is not found. + 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.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + """ + + @abc.abstractmethod + async def delete_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + ) -> stage_instances.StageInstance: + """Delete the Stage instance. + + Parameters + ---------- + channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + The guild stage channel to fetch the Stage instance from. + + Returns + ------- + hikari.stage_instances.StageInstance + The Stage instance that was deleted. + + Raises + ------ + hikari.errors.UnauthorizedError + If you are unauthorized to make the request (invalid/missing token). + hikari.errors.NotFoundError + If the interaction or response is not found. + 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.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + """ diff --git a/hikari/audit_logs.py b/hikari/audit_logs.py index 6b933a50b9..7e71a1f6f4 100644 --- a/hikari/audit_logs.py +++ b/hikari/audit_logs.py @@ -327,6 +327,9 @@ class AuditLogEventType(int, enums.Enum): INTEGRATION_CREATE = 80 INTEGRATION_UPDATE = 81 INTEGRATION_DELETE = 82 + STAGE_INSTANCE_CREATE = 83 + STAGE_INSTANCE_UPDATE = 84 + STAGE_INSTANCE_DELETE = 85 STICKER_CREATE = 90 STICKER_UPDATE = 91 STICKER_DELETE = 92 diff --git a/hikari/events/__init__.py b/hikari/events/__init__.py index 2e858ab8e5..db801f2149 100644 --- a/hikari/events/__init__.py +++ b/hikari/events/__init__.py @@ -37,6 +37,7 @@ from hikari.events.role_events import * from hikari.events.scheduled_events import * from hikari.events.shard_events import * +from hikari.events.stage_events import * from hikari.events.typing_events import * from hikari.events.user_events import * from hikari.events.voice_events import * diff --git a/hikari/events/__init__.pyi b/hikari/events/__init__.pyi index 2bd26128da..46bef00c13 100644 --- a/hikari/events/__init__.pyi +++ b/hikari/events/__init__.pyi @@ -14,6 +14,7 @@ from hikari.events.reaction_events import * from hikari.events.role_events import * from hikari.events.scheduled_events import * from hikari.events.shard_events import * +from hikari.events.stage_events import * from hikari.events.typing_events import * from hikari.events.user_events import * from hikari.events.voice_events import * diff --git a/hikari/events/stage_events.py b/hikari/events/stage_events.py new file mode 100644 index 0000000000..a00b40d367 --- /dev/null +++ b/hikari/events/stage_events.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# cython: language_level=3 +# Copyright (c) 2020 Nekokatt +# Copyright (c) 2021 davfsa +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Events that fire when Stage instances are created/updated/deleted.""" + +from __future__ import annotations + +import abc +import typing + +import attr + +from hikari import intents +from hikari.events import base_events +from hikari.events import shard_events +from hikari.internal import attr_extensions +from hikari.stage_instances import StageInstance + +if typing.TYPE_CHECKING: + from hikari import snowflakes + from hikari import traits + from hikari.api import shard as gateway_shard + + +@base_events.requires_intents(intents.Intents.GUILDS) +class StageInstanceEvent(shard_events.ShardEvent, abc.ABC): + """Event base for any event that involves Stage instances.""" + + __slots__: typing.Sequence[str] = () + + @property + @abc.abstractmethod + def stage_instance_id(self) -> snowflakes.Snowflake: + """ID of the stage instance that this event relates to. + + Returns + ------- + hikari.snowflakes.Snowflake + The ID of the stage instance that this event relates to. + """ + + @property + @abc.abstractmethod + def stage_instance(self) -> StageInstance: + """Stage Instance that this event relates to. + + Returns + ------- + hikari.stage_instance.StageInstance + The Stage Instance that this event relates to. + """ + + +@attr_extensions.with_copy +@attr.define(kw_only=True, weakref_slot=False) +@base_events.requires_intents(intents.Intents.GUILDS) +class StageInstanceCreateEvent(StageInstanceEvent): + """Event fired when a Stage instance is created.""" + + shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True}) + # <>. + + stage_instance: StageInstance = attr.field() + """The Stage instance that was created.""" + + @property + def app(self) -> traits.RESTAware: + # <>. + return self.stage_instance.app + + @property + def stage_instance_id(self) -> snowflakes.Snowflake: + # <>. + return self.stage_instance.id + + +@attr_extensions.with_copy +@attr.define(kw_only=True, weakref_slot=False) +@base_events.requires_intents(intents.Intents.GUILDS) +class StageInstanceEditEvent(StageInstanceEvent): + """Event fired when a Stage instance is edited.""" + + shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True}) + # <>. + + stage_instance: StageInstance = attr.field() + """The Stage instance that was edited.""" + + @property + def app(self) -> traits.RESTAware: + # <>. + return self.stage_instance.app + + @property + def stage_instance_id(self) -> snowflakes.Snowflake: + return self.stage_instance.id + + +@attr_extensions.with_copy +@attr.define(kw_only=True, weakref_slot=False) +@base_events.requires_intents(intents.Intents.GUILDS) +class StageInstanceDeleteEvent(StageInstanceEvent): + """Event fired when a Stage instance is deleted.""" + + shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True}) + # <>. + + stage_instance: StageInstance = attr.field() + """The Stage instance that was deleted.""" + + @property + def app(self) -> traits.RESTAware: + # <>. + return self.stage_instance.app + + @property + def stage_instance_id(self) -> snowflakes.Snowflake: + return self.stage_instance.id diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index a5f6cebf7f..258bb78366 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -51,6 +51,7 @@ from hikari import scheduled_events as scheduled_events_models from hikari import sessions as gateway_models from hikari import snowflakes +from hikari import stage_instances from hikari import stickers as sticker_models from hikari import templates as template_models from hikari import traits @@ -1466,6 +1467,17 @@ def deserialize_guild_private_thread( thread_created_at=thread_created_at, ) + def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_instances.StageInstance: + return stage_instances.StageInstance( + app=self._app, + id=snowflakes.Snowflake(payload["id"]), + channel_id=snowflakes.Snowflake(payload["channel_id"]), + guild_id=snowflakes.Snowflake(payload["guild_id"]), + topic=payload["topic"], + privacy_level=stage_instances.StagePrivacyLevel(payload["privacy_level"]), + discoverable_disabled=payload["discoverable_disabled"], + ) + def deserialize_channel( self, payload: data_binding.JSONObject, diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py index 5e638107c9..269e022714 100644 --- a/hikari/impl/event_factory.py +++ b/hikari/impl/event_factory.py @@ -49,6 +49,7 @@ from hikari.events import role_events from hikari.events import scheduled_events from hikari.events import shard_events +from hikari.events import stage_events from hikari.events import typing_events from hikari.events import user_events from hikari.events import voice_events @@ -928,3 +929,28 @@ def deserialize_voice_server_update_event( return voice_events.VoiceServerUpdateEvent( app=self._app, shard=shard, guild_id=guild_id, token=token, raw_endpoint=raw_endpoint ) + + ######################### + # STAGE INSTANCE EVENTS # + ######################### + + def deserialize_stage_instance_create_event( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> stage_events.StageInstanceCreateEvent: + return stage_events.StageInstanceCreateEvent( + shard=shard, stage_instance=self._app.entity_factory.deserialize_stage_instance(payload) + ) + + def deserialize_stage_instance_edit_event( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> stage_events.StageInstanceEditEvent: + return stage_events.StageInstanceEditEvent( + shard=shard, stage_instance=self._app.entity_factory.deserialize_stage_instance(payload) + ) + + def deserialize_stage_instance_delete_event( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> stage_events.StageInstanceDeleteEvent: + return stage_events.StageInstanceDeleteEvent( + shard=shard, stage_instance=self._app.entity_factory.deserialize_stage_instance(payload) + ) diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py index e9ec111adc..fa03c5fbb2 100644 --- a/hikari/impl/event_manager.py +++ b/hikari/impl/event_manager.py @@ -872,3 +872,18 @@ async def on_guild_audit_log_entry_create( ) -> None: """See https://discord.com/developers/docs/topics/gateway-events#guild-audit-log-entry-create for more info.""" await self.dispatch(self._event_factory.deserialize_audit_log_entry_create_event(shard, payload)) + + async def on_stage_instance_create( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> None: + await self.dispatch(self._event_factory.deserialize_stage_instance_create_event(shard, payload)) + + async def on_stage_instance_update( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> None: + await self.dispatch(self._event_factory.deserialize_stage_instance_edit_event(shard, payload)) + + async def on_stage_instance_delete( + self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject + ) -> None: + await self.dispatch(self._event_factory.deserialize_stage_instance_delete_event(shard, payload)) diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index a24ef7bf40..74cf51365b 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -4377,3 +4377,54 @@ def fetch_scheduled_event_users( return special_endpoints_impl.ScheduledEventUserIterator( self._entity_factory, self._request, newest_first, str(start_at), guild, event ) + + async def fetch_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + ) -> stage_instances.StageInstance: + route = routes.GET_STAGE_INSTANCE.compile(channel=channel) + response = await self._request(route) + assert isinstance(response, dict) + return self._entity_factory.deserialize_stage_instance(response) + + async def create_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + *, + topic: str, + privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, + ) -> stage_instances.StageInstance: + route = routes.POST_STAGE_INSTANCE.compile() + body = data_binding.JSONObjectBuilder() + body.put_snowflake("channel_id", channel) + body.put("topic", topic) + body.put("privacy_level", privacy_level) + + response = await self._request(route, json=body) + assert isinstance(response, dict) + return self._entity_factory.deserialize_stage_instance(response) + + async def edit_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + *, + topic: undefined.UndefinedOr[str] = undefined.UNDEFINED, + privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, + ) -> stage_instances.StageInstance: + route = routes.PATCH_STAGE_INSTANCE.compile(channel=channel) + body = data_binding.JSONObjectBuilder() + body.put("topic", topic) + body.put("privacy_level", privacy_level) + + response = await self._request(route, json=body) + assert isinstance(response, dict) + return self._entity_factory.deserialize_stage_instance(response) + + async def delete_stage_instance( + self, + channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + ) -> stage_instances.StageInstance: + route = routes.DELETE_STAGE_INSTANCE.compile(channel=channel) + response = await self._request(route) + assert isinstance(response, dict) + return self._entity_factory.deserialize_stage_instance(response) diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py index 0db58bbfe2..343f7b696c 100644 --- a/hikari/internal/routes.py +++ b/hikari/internal/routes.py @@ -340,6 +340,11 @@ def compile_to_file( POST_CHANNEL_WEBHOOKS: typing.Final[Route] = Route(POST, "/channels/{channel}/webhooks") GET_CHANNEL_WEBHOOKS: typing.Final[Route] = Route(GET, "/channels/{channel}/webhooks") +POST_STAGE_INSTANCE: typing.Final[Route] = Route(POST, "/stage-instances") +GET_STAGE_INSTANCE: typing.Final[Route] = Route(GET, "/stage-instances/{channel}") +PATCH_STAGE_INSTANCE: typing.Final[Route] = Route(PATCH, "/stage-instances/{channel}") +DELETE_STAGE_INSTANCE: typing.Final[Route] = Route(DELETE, "/stage-instances/{channel}") + # Reactions GET_REACTIONS: typing.Final[Route] = Route(GET, "/channels/{channel}/messages/{message}/reactions/{emoji}") DELETE_ALL_REACTIONS: typing.Final[Route] = Route(DELETE, "/channels/{channel}/messages/{message}/reactions") diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py new file mode 100644 index 0000000000..58c602c655 --- /dev/null +++ b/hikari/stage_instances.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +# cython: language_level=3 +# Copyright (c) 2020 Nekokatt +# Copyright (c) 2021 davfsa +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Application and entities that are used to describe Stage instances on Discord.""" +from __future__ import annotations + +__all__: typing.List[str] = [ + "StagePrivacyLevel", + "StageInstance", +] + +import typing + +import attr + +from hikari import channels +from hikari import snowflakes +from hikari.internal import attr_extensions +from hikari.internal import enums + +if typing.TYPE_CHECKING: + from hikari import guilds + from hikari import traits + + +@typing.final +class StagePrivacyLevel(int, enums.Enum): + """The privacy level of a Stage instance.""" + + PUBLIC = 1 + """The Stage instance is visible publicly.""" + + GUILD = 2 + """The Stage instance is only visible to the guild members""" + + +@attr.define(hash=True, kw_only=True, weakref_slot=False) +class StageInstance(snowflakes.Unique): + """Represents a Stage instance.""" + + id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True) + """ID of the Stage instance.""" + + app: traits.RESTAware = attr.field( + repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} + ) + """The client application that models may use for procedures.""" + + channel_id: snowflakes.Snowflake = attr.field(hash=True, repr=False) + """The channel ID of the Stage instance.""" + + guild_id: snowflakes.Snowflake = attr.field(hash=True, repr=False) + """The guild ID of the Stage instance.""" + + topic: str = attr.field(eq=False, hash=False, repr=False) + """The topic of the Stage instance.""" + + privacy_level: typing.Union[StagePrivacyLevel, int] = attr.field(eq=False, hash=False, repr=False) + """The privacy level of the Stage instance.""" + + discoverable_disabled: bool = attr.field(eq=False, hash=False, repr=False) + """Whether or not Stage discovery is disabled.""" + + def get_channel(self) -> typing.Optional[channels.GuildStageChannel]: + """Return the guild stage channel where this stage instance was created. + + This will be empty if the channels are missing from the cache. + + Returns + ------- + hikari.channels.GuildStageChannel + The guild stage channel where this stage instance was created. + """ + if not isinstance(self.app, traits.CacheAware): + return None + + channel = self.app.cache.get_guild_channel(self.channel_id) + assert isinstance(channel, channels.GuildStageChannel) + + return channel + + async def fetch_channel(self) -> channels.GuildStageChannel: + """Fetch the stage channel where this stage instance was created. + + Returns + ------- + hikari.channels.GuildStageChannel + The stage channel where this stage instance was created. + + Raises + ------ + hikari.errors.BadRequestError + If any invalid snowflake IDs are passed; a snowflake may be invalid + due to it being outside of the range of a 64 bit integer. + hikari.errors.ForbiddenError + If you don't have access to the channel this message belongs to. + hikari.errors.NotFoundError + If the channel this message was created in does not exist. + 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.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + """ + channel = await self.app.rest.fetch_channel(self.channel_id) + assert isinstance(channel, channels.GuildStageChannel) + + return channel + + def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: + """Get the cached guild that this Stage Instance relates to, if known. + + If not known, this will return `builtins.None` instead. + + Returns + ------- + typing.Optional[hikari.guilds.GatewayGuild] + The guild this event relates to, or `builtins.None` if not known. + """ + if not isinstance(self.app, traits.CacheAware): + return None + return self.app.cache.get_guild(self.guild_id) + + async def fetch_guild(self) -> guilds.RESTGuild: + """Fetch the guild linked to this Stage Instance. + + Returns + ------- + hikari.guilds.RESTGuild + The guild linked to this Stage Instance + + Raises + ------ + hikari.errors.ForbiddenError + If you are not part of the guild. + 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.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + """ + return await self.app.rest.fetch_guild(self.guild_id) diff --git a/tests/hikari/events/test_stage_events.py b/tests/hikari/events/test_stage_events.py new file mode 100644 index 0000000000..1e702d8bc2 --- /dev/null +++ b/tests/hikari/events/test_stage_events.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Nekokatt +# Copyright (c) 2021 davfsa +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import mock +import pytest + +from hikari import stage_instances +from hikari.events import stage_events + + +class TestStageInstanceCreateEvent: + @pytest.fixture() + def event(self): + return stage_events.StageInstanceCreateEvent( + shard=object(), stage_instance=mock.Mock(stage_instances.StageInstance) + ) + + def test_app_property(self, event): + assert event.app is event.stage_instance.app + + def test_stage_instance_id_property(self, event): + event.stage_instance.id = 1234 + assert event.stage_instance_id == 1234 + + +class TestStageInstanceEditEvent: + @pytest.fixture() + def event(self): + return stage_events.StageInstanceEditEvent( + shard=object(), stage_instance=mock.Mock(stage_instances.StageInstance) + ) + + def test_app_property(self, event): + assert event.app is event.stage_instance.app + + def test_stage_instance_id_property(self, event): + event.stage_instance.id = 1234 + assert event.stage_instance_id == 1234 + + +class TestStageInstanceDeleteEvent: + @pytest.fixture() + def event(self): + return stage_events.StageInstanceDeleteEvent( + shard=object(), stage_instance=mock.Mock(stage_instances.StageInstance) + ) + + def test_app_property(self, event): + assert event.app is event.stage_instance.app + + def test_stage_instance_id_property(self, event): + event.stage_instance.id = 1234 + assert event.stage_instance_id == 1234 diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py index 35d07ed180..fa902967b6 100644 --- a/tests/hikari/impl/test_entity_factory.py +++ b/tests/hikari/impl/test_entity_factory.py @@ -7043,3 +7043,29 @@ def test_deserialize_webhook(self, mock_app, type_, fn): def test_deserialize_webhook_for_unexpected_webhook_type(self, entity_factory_impl): with pytest.raises(errors.UnrecognisedEntityError): entity_factory_impl.deserialize_webhook({"type": -7999}) + + ######################### + # Stage instance models # + ######################### + + @pytest.fixture() + def stage_instance_payload(self): + return { + "id": "840647391636226060", + "guild_id": "197038439483310086", + "channel_id": "733488538393510049", + "topic": "Testing Testing, 123", + "privacy_level": 1, + "discoverable_disabled": False, + } + + def test_deserialize_stage_instance(self, entity_factory_impl, stage_instance_payload, mock_app): + stage_instance = entity_factory_impl.deserialize_stage_instance(stage_instance_payload) + + assert stage_instance.app is mock_app + assert stage_instance.id == 840647391636226060 + assert stage_instance.channel_id == 733488538393510049 + assert stage_instance.guild_id == 197038439483310086 + assert stage_instance.topic == "Testing Testing, 123" + assert stage_instance.privacy_level == 1 + assert stage_instance.discoverable_disabled is False diff --git a/tests/hikari/impl/test_event_factory.py b/tests/hikari/impl/test_event_factory.py index 1cb966827d..3c4e342069 100644 --- a/tests/hikari/impl/test_event_factory.py +++ b/tests/hikari/impl/test_event_factory.py @@ -41,6 +41,7 @@ from hikari.events import role_events from hikari.events import scheduled_events from hikari.events import shard_events +from hikari.events import stage_events from hikari.events import typing_events from hikari.events import user_events from hikari.events import voice_events @@ -1458,3 +1459,58 @@ def test_deserialize_voice_server_update_event(self, event_factory, mock_app, mo assert event.token == "okokok" assert event.guild_id == 3122312 assert event.raw_endpoint == "httppppppp" + + ######################### + # STAGE INSTANCE EVENTS # + ######################### + + def test_deserialize_stage_instance_create_event(self, event_factory, mock_app, mock_shard): + mock_payload = { + "id": "840647391636226060", + "guild_id": "197038439483310086", + "channel_id": "733488538393510049", + "topic": "Testing Testing, 123", + "privacy_level": 1, + "discoverable_disabled": False, + } + event = event_factory.deserialize_stage_instance_create_event(mock_shard, mock_payload) + assert isinstance(event, stage_events.StageInstanceCreateEvent) + + assert event.shard is mock_shard + assert event.app is event.stage_instance.app + assert event.stage_instance_id == mock_app.entity_factory.deserialize_stage_instance.return_value.id + assert event.stage_instance == mock_app.entity_factory.deserialize_stage_instance.return_value + + def test_deserialize_stage_instance_edit_event(self, event_factory, mock_app, mock_shard): + mock_payload = { + "id": "840647391636226060", + "guild_id": "197038439483310086", + "channel_id": "733488538393510049", + "topic": "Testing Testing, 124", + "privacy_level": 2, + "discoverable_disabled": True, + } + event = event_factory.deserialize_stage_instance_edit_event(mock_shard, mock_payload) + assert isinstance(event, stage_events.StageInstanceEditEvent) + + assert event.shard is mock_shard + assert event.app is event.stage_instance.app + assert event.stage_instance_id == mock_app.entity_factory.deserialize_stage_instance.return_value.id + assert event.stage_instance == mock_app.entity_factory.deserialize_stage_instance.return_value + + def test_deserialize_stage_instance_delete_event(self, event_factory, mock_app, mock_shard): + mock_payload = { + "id": "840647391636226060", + "guild_id": "197038439483310086", + "channel_id": "733488538393510049", + "topic": "Testing Testing, 124", + "privacy_level": 2, + "discoverable_disabled": True, + } + event = event_factory.deserialize_stage_instance_delete_event(mock_shard, mock_payload) + assert isinstance(event, stage_events.StageInstanceDeleteEvent) + + assert event.shard is mock_shard + assert event.app is event.stage_instance.app + assert event.stage_instance_id == mock_app.entity_factory.deserialize_stage_instance.return_value.id + assert event.stage_instance == mock_app.entity_factory.deserialize_stage_instance.return_value diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py index 3068c41616..c482b08b22 100644 --- a/tests/hikari/impl/test_event_manager.py +++ b/tests/hikari/impl/test_event_manager.py @@ -1692,3 +1692,57 @@ async def test_on_guild_audit_log_entry_create( event_manager_impl.dispatch.assert_awaited_once_with( event_factory.deserialize_audit_log_entry_create_event.return_value ) + + @pytest.mark.asyncio() + async def test_on_stage_instance_create(self, event_manager, shard, event_factory): + payload = { + "id": "840647391636226060", + "guild_id": "197038439483310086", + "channel_id": "733488538393510049", + "topic": "Testing Testing, 123", + "privacy_level": 1, + "discoverable_disabled": False, + } + + await event_manager.on_stage_instance_create(shard, payload) + + event_factory.deserialize_stage_instance_create_event.assert_called_once_with(shard, payload) + event_manager.dispatch.assert_awaited_once_with( + event_factory.deserialize_stage_instance_create_event.return_value + ) + + @pytest.mark.asyncio() + async def test_on_stage_instance_update(self, event_manager, shard, event_factory): + payload = { + "id": "840647391636226060", + "guild_id": "197038439483310086", + "channel_id": "733488538393510049", + "topic": "Testing Testing, 123", + "privacy_level": 1, + "discoverable_disabled": False, + } + + await event_manager.on_stage_instance_update(shard, payload) + + event_factory.deserialize_stage_instance_edit_event.assert_called_once_with(shard, payload) + event_manager.dispatch.assert_awaited_once_with( + event_factory.deserialize_stage_instance_edit_event.return_value + ) + + @pytest.mark.asyncio() + async def test_on_stage_instance_delete(self, event_manager, shard, event_factory): + payload = { + "id": "840647391636226060", + "guild_id": "197038439483310086", + "channel_id": "733488538393510049", + "topic": "Testing Testing, 123", + "privacy_level": 1, + "discoverable_disabled": False, + } + + await event_manager.on_stage_instance_delete(shard, payload) + + event_factory.deserialize_stage_instance_delete_event.assert_called_once_with(shard, payload) + event_manager.dispatch.assert_awaited_once_with( + event_factory.deserialize_stage_instance_delete_event.return_value + ) diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py index 142f043fa1..9f17dc9e8d 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -6405,3 +6405,77 @@ async def test_delete_scheduled_event(self, rest_client: rest.RESTClientImpl): await rest_client.delete_scheduled_event(StubModel(54531123), StubModel(123321123321)) rest_client._request.assert_awaited_once_with(expected_route) + + async def test_fetch_stage_instance(self, rest_client): + expected_route = routes.GET_STAGE_INSTANCE.compile(channel=123) + mock_payload = { + "id": "8406", + "guild_id": "19703", + "channel_id": "123", + "topic": "ur mom", + "privacy_level": 1, + "discoverable_disabled": False, + } + rest_client._request = mock.AsyncMock(return_value=mock_payload) + + result = await rest_client.fetch_stage_instance(channel=123) + + assert result is rest_client._entity_factory.deserialize_stage_instance.return_value + rest_client._request.assert_called_once_with(expected_route) + rest_client._entity_factory.deserialize_stage_instance.assert_called_once_with(mock_payload) + + async def test_create_stage_instance(self, rest_client): + expected_route = routes.POST_STAGE_INSTANCE.compile() + expected_json = {"channel_id":"7334", "privacy_level": 1, "topic":"ur mom"} + mock_payload = { + "id": "8406", + "guild_id": "19703", + "channel_id": "7334", + "topic": "ur mom", + "privacy_level": 1, + "discoverable_disabled": False, + } + rest_client._request = mock.AsyncMock(return_value=mock_payload) + + result = await rest_client.create_stage_instance(channel=7334, privacy_level=1, topic="ur mom") + + assert result is rest_client._entity_factory.deserialize_stage_instance.return_value + rest_client._request.assert_called_once_with(expected_route, json=expected_json) + rest_client._entity_factory.deserialize_stage_instance.assert_called_once_with(mock_payload) + + async def test_edit_stage_instance(self, rest_client): + expected_route = routes.PATCH_STAGE_INSTANCE.compile(channel=7334) + expected_json = {"privacy_level": 1, "topic":"ur mom"} + mock_payload = { + "id": "8406", + "guild_id": "19703", + "channel_id": "7334", + "topic": "ur mom", + "privacy_level": 1, + "discoverable_disabled": False, + } + rest_client._request = mock.AsyncMock(return_value=mock_payload) + + result = await rest_client.edit_stage_instance(channel=7334, privacy_level=1, topic="ur mom") + + assert result is rest_client._entity_factory.deserialize_stage_instance.return_value + rest_client._request.assert_called_once_with(expected_route, json=expected_json) + rest_client._entity_factory.deserialize_stage_instance.assert_called_once_with(mock_payload) + + async def test_delete_stage_instance(self, rest_client): + expected_route = routes.DELETE_STAGE_INSTANCE.compile(channel=7334) + mock_payload = { + "id": "8406", + "guild_id": "19703", + "channel_id": "7334", + "topic": "ur mom", + "privacy_level": 1, + "discoverable_disabled": False, + } + rest_client._request = mock.AsyncMock(return_value=mock_payload) + + result = await rest_client.delete_stage_instance(channel=7334) + + assert result is rest_client._entity_factory.deserialize_stage_instance.return_value + rest_client._request.assert_called_once_with(expected_route) + rest_client._entity_factory.deserialize_stage_instance.assert_called_once_with(mock_payload) diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py new file mode 100644 index 0000000000..dfb239a181 --- /dev/null +++ b/tests/hikari/test_stage_instances.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020 Nekokatt +# Copyright (c) 2021 davfsa +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import mock +import pytest + +from hikari import channels +from hikari import snowflakes +from hikari import stage_instances +from hikari.impl import bot + + +@pytest.fixture() +def mock_app(): + return mock.Mock(spec_set=bot.GatewayBot) + + +class TestStageInstance: + @pytest.fixture() + async def stage_instance(self, mock_app): + return stage_instances.StageInstance( + id=snowflakes.Snowflake(123), + app=mock_app, + channel_id=snowflakes.Snowflake(6969), + guild_id=snowflakes.Snowflake(420), + topic="beanos", + privacy_level=stage_instances.StagePrivacyLevel.PUBLIC, + discoverable_disabled=True, + ) + + def test_id_property(self, stage_instance): + assert stage_instance.id == 123 + + def test_app_property(self, stage_instance, mock_app): + assert stage_instance.app is mock_app + + def test_channel_id_property(self, stage_instance): + assert stage_instance.channel_id == 6969 + + def test_guild_id_property(self, stage_instance): + assert stage_instance.guild_id == 420 + + def test_topic_property(self, stage_instance): + assert stage_instance.topic == "beanos" + + def test_privacy_level_property(self, stage_instance): + assert stage_instance.privacy_level == 1 + + def test_discoverable_disabled_property(self, stage_instance): + assert stage_instance.discoverable_disabled is True + + @pytest.mark.asyncio() + async def test_fetch_channel(self, stage_instance): + mock_channel = mock.Mock(channels.GuildStageChannel) + stage_instance.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel) + + await stage_instance.fetch_channel() == stage_instance.app.rest.fetch_channel.result_value + stage_instance.app.rest.fetch_channel.assert_awaited_once_with(6969) + + @pytest.mark.asyncio() + async def test_fetch_guild(self, stage_instance): + stage_instance.app.rest.fetch_guild = mock.AsyncMock() + + await stage_instance.fetch_guild() == stage_instance.app.rest.fetch_guild.return_value + + stage_instance.app.rest.fetch_guild.assert_awaited_once_with(420) From 67996b898949c9774af29b518f63f01279110cbc Mon Sep 17 00:00:00 2001 From: nulldomain Date: Thu, 28 Sep 2023 18:21:57 +0100 Subject: [PATCH 02/30] Rebased and improved Just brought it up to date with the current version (at time of writing) --- hikari/api/rest.py | 7 +++-- hikari/audit_logs.py | 6 ++--- hikari/events/stage_events.py | 14 +++++----- hikari/impl/rest.py | 7 +++-- hikari/stage_instances.py | 9 +++---- tests/hikari/events/test_stage_events.py | 2 +- tests/hikari/impl/test_event_manager.py | 33 +++++++++++++++++------- tests/hikari/impl/test_rest.py | 4 +-- tests/hikari/test_stage_instances.py | 13 +++++----- 9 files changed, 52 insertions(+), 43 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 7f6a040c28..b9c4591136 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8220,9 +8220,9 @@ def fetch_scheduled_event_users( If an internal error occurs on Discord while handling the request. """ + @abc.abstractmethod async def fetch_stage_instance( - self, - channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] ) -> stage_instances.StageInstance: """Fetch the Stage instance associated with a guild stage channel. @@ -8365,8 +8365,7 @@ async def edit_stage_instance( @abc.abstractmethod async def delete_stage_instance( - self, - channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] ) -> stage_instances.StageInstance: """Delete the Stage instance. diff --git a/hikari/audit_logs.py b/hikari/audit_logs.py index 7e71a1f6f4..fe3d06c635 100644 --- a/hikari/audit_logs.py +++ b/hikari/audit_logs.py @@ -327,9 +327,9 @@ class AuditLogEventType(int, enums.Enum): INTEGRATION_CREATE = 80 INTEGRATION_UPDATE = 81 INTEGRATION_DELETE = 82 - STAGE_INSTANCE_CREATE = 83 - STAGE_INSTANCE_UPDATE = 84 - STAGE_INSTANCE_DELETE = 85 + STAGE_INSTANCE_CREATE = 83 + STAGE_INSTANCE_UPDATE = 84 + STAGE_INSTANCE_DELETE = 85 STICKER_CREATE = 90 STICKER_UPDATE = 91 STICKER_DELETE = 92 diff --git a/hikari/events/stage_events.py b/hikari/events/stage_events.py index a00b40d367..467dd37175 100644 --- a/hikari/events/stage_events.py +++ b/hikari/events/stage_events.py @@ -32,7 +32,7 @@ from hikari import intents from hikari.events import base_events from hikari.events import shard_events -from hikari.internal import attr_extensions +from hikari.internal import attrs_extensions from hikari.stage_instances import StageInstance if typing.TYPE_CHECKING: @@ -70,13 +70,13 @@ def stage_instance(self) -> StageInstance: """ -@attr_extensions.with_copy +@attrs_extensions.with_copy @attr.define(kw_only=True, weakref_slot=False) @base_events.requires_intents(intents.Intents.GUILDS) class StageInstanceCreateEvent(StageInstanceEvent): """Event fired when a Stage instance is created.""" - shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True}) + shard: gateway_shard.GatewayShard = attr.field(metadata={attrs_extensions.SKIP_DEEP_COPY: True}) # <>. stage_instance: StageInstance = attr.field() @@ -93,13 +93,13 @@ def stage_instance_id(self) -> snowflakes.Snowflake: return self.stage_instance.id -@attr_extensions.with_copy +@attrs_extensions.with_copy @attr.define(kw_only=True, weakref_slot=False) @base_events.requires_intents(intents.Intents.GUILDS) class StageInstanceEditEvent(StageInstanceEvent): """Event fired when a Stage instance is edited.""" - shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True}) + shard: gateway_shard.GatewayShard = attr.field(metadata={attrs_extensions.SKIP_DEEP_COPY: True}) # <>. stage_instance: StageInstance = attr.field() @@ -115,13 +115,13 @@ def stage_instance_id(self) -> snowflakes.Snowflake: return self.stage_instance.id -@attr_extensions.with_copy +@attrs_extensions.with_copy @attr.define(kw_only=True, weakref_slot=False) @base_events.requires_intents(intents.Intents.GUILDS) class StageInstanceDeleteEvent(StageInstanceEvent): """Event fired when a Stage instance is deleted.""" - shard: gateway_shard.GatewayShard = attr.field(metadata={attr_extensions.SKIP_DEEP_COPY: True}) + shard: gateway_shard.GatewayShard = attr.field(metadata={attrs_extensions.SKIP_DEEP_COPY: True}) # <>. stage_instance: StageInstance = attr.field() diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index 74cf51365b..ada3b0db4a 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -62,6 +62,7 @@ from hikari import permissions as permissions_ from hikari import scheduled_events from hikari import snowflakes +from hikari import stage_instances from hikari import traits from hikari import undefined from hikari import urls @@ -4379,8 +4380,7 @@ def fetch_scheduled_event_users( ) async def fetch_stage_instance( - self, - channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] ) -> stage_instances.StageInstance: route = routes.GET_STAGE_INSTANCE.compile(channel=channel) response = await self._request(route) @@ -4421,8 +4421,7 @@ async def edit_stage_instance( return self._entity_factory.deserialize_stage_instance(response) async def delete_stage_instance( - self, - channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], + self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] ) -> stage_instances.StageInstance: route = routes.DELETE_STAGE_INSTANCE.compile(channel=channel) response = await self._request(route) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 58c602c655..45ec6e2d8c 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -23,10 +23,7 @@ """Application and entities that are used to describe Stage instances on Discord.""" from __future__ import annotations -__all__: typing.List[str] = [ - "StagePrivacyLevel", - "StageInstance", -] +__all__: typing.List[str] = ["StagePrivacyLevel", "StageInstance"] import typing @@ -34,7 +31,7 @@ from hikari import channels from hikari import snowflakes -from hikari.internal import attr_extensions +from hikari.internal import attrs_extensions from hikari.internal import enums if typing.TYPE_CHECKING: @@ -61,7 +58,7 @@ class StageInstance(snowflakes.Unique): """ID of the Stage instance.""" app: traits.RESTAware = attr.field( - repr=False, eq=False, hash=False, metadata={attr_extensions.SKIP_DEEP_COPY: True} + repr=False, eq=False, hash=False, metadata={attrs_extensions.SKIP_DEEP_COPY: True} ) """The client application that models may use for procedures.""" diff --git a/tests/hikari/events/test_stage_events.py b/tests/hikari/events/test_stage_events.py index 1e702d8bc2..0e46e5aef6 100644 --- a/tests/hikari/events/test_stage_events.py +++ b/tests/hikari/events/test_stage_events.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020 Nekokatt -# Copyright (c) 2021 davfsa +# Copyright (c) 2021-present davfsa # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py index c482b08b22..d5cc85a0ea 100644 --- a/tests/hikari/impl/test_event_manager.py +++ b/tests/hikari/impl/test_event_manager.py @@ -1694,7 +1694,12 @@ async def test_on_guild_audit_log_entry_create( ) @pytest.mark.asyncio() - async def test_on_stage_instance_create(self, event_manager, shard, event_factory): + async def test_on_stage_instance_create( + self, + event_manager_impl: event_manager.EventManagerImpl, + shard: mock.Mock, + event_factory: event_factory_.EventFactory, + ): payload = { "id": "840647391636226060", "guild_id": "197038439483310086", @@ -1704,15 +1709,20 @@ async def test_on_stage_instance_create(self, event_manager, shard, event_factor "discoverable_disabled": False, } - await event_manager.on_stage_instance_create(shard, payload) + await event_manager_impl.on_stage_instance_create(shard, payload) event_factory.deserialize_stage_instance_create_event.assert_called_once_with(shard, payload) - event_manager.dispatch.assert_awaited_once_with( + event_manager_impl.dispatch.assert_awaited_once_with( event_factory.deserialize_stage_instance_create_event.return_value ) @pytest.mark.asyncio() - async def test_on_stage_instance_update(self, event_manager, shard, event_factory): + async def test_on_stage_instance_update( + self, + event_manager_impl: event_manager.EventManagerImpl, + shard: mock.Mock, + event_factory: event_factory_.EventFactory, + ): payload = { "id": "840647391636226060", "guild_id": "197038439483310086", @@ -1722,15 +1732,20 @@ async def test_on_stage_instance_update(self, event_manager, shard, event_factor "discoverable_disabled": False, } - await event_manager.on_stage_instance_update(shard, payload) + await event_manager_impl.on_stage_instance_update(shard, payload) event_factory.deserialize_stage_instance_edit_event.assert_called_once_with(shard, payload) - event_manager.dispatch.assert_awaited_once_with( + event_manager_impl.dispatch.assert_awaited_once_with( event_factory.deserialize_stage_instance_edit_event.return_value ) @pytest.mark.asyncio() - async def test_on_stage_instance_delete(self, event_manager, shard, event_factory): + async def test_on_stage_instance_delete( + self, + event_manager_impl: event_manager.EventManagerImpl, + shard: mock.Mock, + event_factory: event_factory_.EventFactory, + ): payload = { "id": "840647391636226060", "guild_id": "197038439483310086", @@ -1740,9 +1755,9 @@ async def test_on_stage_instance_delete(self, event_manager, shard, event_factor "discoverable_disabled": False, } - await event_manager.on_stage_instance_delete(shard, payload) + await event_manager_impl.on_stage_instance_delete(shard, payload) event_factory.deserialize_stage_instance_delete_event.assert_called_once_with(shard, payload) - event_manager.dispatch.assert_awaited_once_with( + event_manager_impl.dispatch.assert_awaited_once_with( event_factory.deserialize_stage_instance_delete_event.return_value ) diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py index 9f17dc9e8d..3ccb142685 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -6426,7 +6426,7 @@ async def test_fetch_stage_instance(self, rest_client): async def test_create_stage_instance(self, rest_client): expected_route = routes.POST_STAGE_INSTANCE.compile() - expected_json = {"channel_id":"7334", "privacy_level": 1, "topic":"ur mom"} + expected_json = {"channel_id": "7334", "privacy_level": 1, "topic": "ur mom"} mock_payload = { "id": "8406", "guild_id": "19703", @@ -6445,7 +6445,7 @@ async def test_create_stage_instance(self, rest_client): async def test_edit_stage_instance(self, rest_client): expected_route = routes.PATCH_STAGE_INSTANCE.compile(channel=7334) - expected_json = {"privacy_level": 1, "topic":"ur mom"} + expected_json = {"privacy_level": 1, "topic": "ur mom"} mock_payload = { "id": "8406", "guild_id": "19703", diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index dfb239a181..3beb0aa1c4 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020 Nekokatt -# Copyright (c) 2021 davfsa +# Copyright (c) 2021-present davfsa # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,7 @@ from hikari import channels from hikari import snowflakes from hikari import stage_instances -from hikari.impl import bot +from hikari.impl import gateway_bot as bot @pytest.fixture() @@ -36,10 +36,10 @@ def mock_app(): class TestStageInstance: @pytest.fixture() - async def stage_instance(self, mock_app): + def stage_instance(self, mock_app): return stage_instances.StageInstance( - id=snowflakes.Snowflake(123), app=mock_app, + id=snowflakes.Snowflake(123), channel_id=snowflakes.Snowflake(6969), guild_id=snowflakes.Snowflake(420), topic="beanos", @@ -73,13 +73,12 @@ async def test_fetch_channel(self, stage_instance): mock_channel = mock.Mock(channels.GuildStageChannel) stage_instance.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel) - await stage_instance.fetch_channel() == stage_instance.app.rest.fetch_channel.result_value + assert await stage_instance.fetch_channel() == stage_instance.app.rest.fetch_channel.result_value stage_instance.app.rest.fetch_channel.assert_awaited_once_with(6969) @pytest.mark.asyncio() async def test_fetch_guild(self, stage_instance): stage_instance.app.rest.fetch_guild = mock.AsyncMock() - await stage_instance.fetch_guild() == stage_instance.app.rest.fetch_guild.return_value - + assert await stage_instance.fetch_guild() == stage_instance.app.rest.fetch_guild.return_value stage_instance.app.rest.fetch_guild.assert_awaited_once_with(420) From f5704cce6512ab9b0257e3b35d70813c130cf0fe Mon Sep 17 00:00:00 2001 From: nulldomain Date: Thu, 28 Sep 2023 18:22:05 +0100 Subject: [PATCH 03/30] Fix failing test --- tests/hikari/test_stage_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 3beb0aa1c4..31ce1bd48b 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -70,10 +70,10 @@ def test_discoverable_disabled_property(self, stage_instance): @pytest.mark.asyncio() async def test_fetch_channel(self, stage_instance): - mock_channel = mock.Mock(channels.GuildStageChannel) - stage_instance.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_channel) + mock_stage_channel = mock.Mock(channels.GuildStageChannel) + stage_instance.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_stage_channel) - assert await stage_instance.fetch_channel() == stage_instance.app.rest.fetch_channel.result_value + assert await stage_instance.fetch_channel() == mock_stage_channel stage_instance.app.rest.fetch_channel.assert_awaited_once_with(6969) @pytest.mark.asyncio() From ddef5a20b472cd822002a6ec5ca2c66e5d1fefe8 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Thu, 28 Sep 2023 18:22:19 +0100 Subject: [PATCH 04/30] Don't cry, towncrier --- changes/1725.feature.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/1725.feature.md diff --git a/changes/1725.feature.md b/changes/1725.feature.md new file mode 100644 index 0000000000..c9c02fd0cb --- /dev/null +++ b/changes/1725.feature.md @@ -0,0 +1 @@ +Implement stage instances From d99b5d533ed6f7bb7aeab6513f30ad8b4defe295 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Mon, 2 Oct 2023 10:31:00 +0100 Subject: [PATCH 05/30] Add missing attributes to Stage instances `guild_scheduled_event_id` was recently merged into DAPI docs. `send_start_notification` was added to Create Stage Instance some time ago, so I added that in too. --- hikari/api/rest.py | 13 +++++++- hikari/impl/entity_factory.py | 1 + hikari/impl/rest.py | 6 ++++ hikari/stage_instances.py | 44 +++++++++++++++++++++++++++- tests/hikari/test_stage_instances.py | 4 +++ 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index b9c4591136..a4cfc8b351 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8266,6 +8266,10 @@ async def create_stage_instance( *, topic: str, privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, + send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, + guild_scheduled_event_id: undefined.UndefinedOr[ + snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] + ] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: """Create a Stage instance in guild stage channel. @@ -8284,6 +8288,13 @@ async def create_stage_instance( This will be set to `hikari.stage_instances.StagePrivacyLevel.GUILD_ONLY` if not provided. + send_start_notification: hikari.undefined.UndefinedOr[builtins.bool] + Whether to notify @everyone that the Stage instance has started. + + guild_scheduled_event_id: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.scheduled_events.ScheduledEvent]] + The ID of the scheduled event to associate with the Stage instance. + + Returns ------- hikari.stage_instances.StageInstance @@ -8308,7 +8319,7 @@ async def create_stage_instance( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - """ + """ # noqa: E501 - Line too long @abc.abstractmethod async def edit_stage_instance( diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 258bb78366..15cf729a92 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1476,6 +1476,7 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ topic=payload["topic"], privacy_level=stage_instances.StagePrivacyLevel(payload["privacy_level"]), discoverable_disabled=payload["discoverable_disabled"], + guild_scheduled_event_id=payload.get("guild_scheduled_event_id", undefined.UNDEFINED), ) def deserialize_channel( diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index ada3b0db4a..c0162b70b4 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -4393,12 +4393,18 @@ async def create_stage_instance( *, topic: str, privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, + send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, + guild_scheduled_event_id: undefined.UndefinedOr[ + snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] + ] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: route = routes.POST_STAGE_INSTANCE.compile() body = data_binding.JSONObjectBuilder() body.put_snowflake("channel_id", channel) body.put("topic", topic) body.put("privacy_level", privacy_level) + body.put("send_start_notification", send_start_notification) + body.put_snowflake("guild_scheduled_event_id", guild_scheduled_event_id) response = await self._request(route, json=body) assert isinstance(response, dict) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 45ec6e2d8c..b9eccedc2b 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -30,7 +30,9 @@ import attr from hikari import channels +from hikari import scheduled_events from hikari import snowflakes +from hikari import undefined from hikari.internal import attrs_extensions from hikari.internal import enums @@ -44,7 +46,7 @@ class StagePrivacyLevel(int, enums.Enum): """The privacy level of a Stage instance.""" PUBLIC = 1 - """The Stage instance is visible publicly.""" + """The Stage instance is visible publicly. (Deprecated)""" GUILD = 2 """The Stage instance is only visible to the guild members""" @@ -77,6 +79,11 @@ class StageInstance(snowflakes.Unique): discoverable_disabled: bool = attr.field(eq=False, hash=False, repr=False) """Whether or not Stage discovery is disabled.""" + guild_scheduled_event_id: undefined.UndefinedOr[ + snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] + ] = attr.field(eq=False, hash=False, repr=False) + "The ID of the scheduled event for this Stage instance, if it exists." + def get_channel(self) -> typing.Optional[channels.GuildStageChannel]: """Return the guild stage channel where this stage instance was created. @@ -178,3 +185,38 @@ async def fetch_guild(self) -> guilds.RESTGuild: If an internal error occurs on Discord while handling the request. """ return await self.app.rest.fetch_guild(self.guild_id) + + async def fetch_scheduled_event(self) -> typing.Optional[scheduled_events.ScheduledEvent]: + """Fetch the scheduled event for this Stage Instance. + + Returns + ------- + typing.Optional[hikari.scheduled_events.ScheduledEvent] + The scheduled event for this Stage Instance, if it exists. + + Raises + ------ + hikari.errors.ForbiddenError + If you are not part of the guild. + 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.RateLimitedError + Usually, Hikari will handle and retry on hitting + rate-limits automatically. This includes most bucket-specific + rate-limits and global rate-limits. In some rare edge cases, + however, Discord implements other undocumented rules for + rate-limiting, such as limits per attribute. These cannot be + detected or handled normally by Hikari due to their undocumented + nature, and will trigger this exception if they occur. + hikari.errors.InternalServerError + If an internal error occurs on Discord while handling the request. + """ + if self.guild_scheduled_event_id is undefined.UNDEFINED: + return None + + return await self.app.rest.fetch_scheduled_event(self.guild_id, self.guild_scheduled_event_id) diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 31ce1bd48b..de252241b3 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -45,6 +45,7 @@ def stage_instance(self, mock_app): topic="beanos", privacy_level=stage_instances.StagePrivacyLevel.PUBLIC, discoverable_disabled=True, + guild_scheduled_event_id=snowflakes.Snowflake(1337), ) def test_id_property(self, stage_instance): @@ -68,6 +69,9 @@ def test_privacy_level_property(self, stage_instance): def test_discoverable_disabled_property(self, stage_instance): assert stage_instance.discoverable_disabled is True + def test_guild_scheduled_event_id_property(self, stage_instance): + assert stage_instance.guild_scheduled_event_id == 1337 + @pytest.mark.asyncio() async def test_fetch_channel(self, stage_instance): mock_stage_channel = mock.Mock(channels.GuildStageChannel) From f7fd4d528adc5f3d882252e0c2a4211458325829 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 08:57:39 +0100 Subject: [PATCH 06/30] Don't use spec_set in stageinstance mock app test Co-authored-by: davfsa Signed-off-by: nulldomain --- tests/hikari/test_stage_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index de252241b3..618bf9195a 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -31,7 +31,7 @@ @pytest.fixture() def mock_app(): - return mock.Mock(spec_set=bot.GatewayBot) + return mock.Mock() class TestStageInstance: From 1a77a6ab8fdcfc03540268d24e016cdb57919eb6 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:09:11 +0100 Subject: [PATCH 07/30] Update tests/hikari/events/test_stage_events.py Co-authored-by: davfsa Signed-off-by: nulldomain --- tests/hikari/events/test_stage_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hikari/events/test_stage_events.py b/tests/hikari/events/test_stage_events.py index 0e46e5aef6..18b5be60fe 100644 --- a/tests/hikari/events/test_stage_events.py +++ b/tests/hikari/events/test_stage_events.py @@ -31,7 +31,7 @@ class TestStageInstanceCreateEvent: @pytest.fixture() def event(self): return stage_events.StageInstanceCreateEvent( - shard=object(), stage_instance=mock.Mock(stage_instances.StageInstance) + shard=object(), stage_instance=mock.Mock() ) def test_app_property(self, event): From f42191a0ec375c6dc1589c252e6e5a545cd56120 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 08:56:33 +0100 Subject: [PATCH 08/30] Remove deprecated stage instance privacy level --- hikari/api/rest.py | 10 ---------- hikari/impl/entity_factory.py | 1 - hikari/impl/rest.py | 4 ---- hikari/stage_instances.py | 16 +--------------- tests/hikari/test_stage_instances.py | 4 ---- 5 files changed, 1 insertion(+), 34 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index a4cfc8b351..4ea2f449a6 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8265,7 +8265,6 @@ async def create_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: str, - privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, guild_scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] @@ -8283,11 +8282,6 @@ async def create_stage_instance( topic: builtins.str The topic for the Stage instance. - privacy_level: hikari.undefined.UndefinedOr[hikari.stage_instances.StagePrivacyLevel] - The privacy level of the Stage Instance. - - This will be set to `hikari.stage_instances.StagePrivacyLevel.GUILD_ONLY` if not provided. - send_start_notification: hikari.undefined.UndefinedOr[builtins.bool] Whether to notify @everyone that the Stage instance has started. @@ -8327,7 +8321,6 @@ async def edit_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: undefined.UndefinedOr[str] = undefined.UNDEFINED, - privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: """Edit the Stage instance in a guild stage channel. @@ -8345,9 +8338,6 @@ async def edit_stage_instance( topic: hikari.undefined.UndefinedOr[builtins.str] The topic for the Stage instance. - privacy_level: hikari.undefined.UndefinedOr[hikari.stage_instances.StagePrivacyLevel] - The privacy level of the Stage Instance. - Returns ------- hikari.stage_instances.StageInstance diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 15cf729a92..eaba415c23 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1474,7 +1474,6 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ channel_id=snowflakes.Snowflake(payload["channel_id"]), guild_id=snowflakes.Snowflake(payload["guild_id"]), topic=payload["topic"], - privacy_level=stage_instances.StagePrivacyLevel(payload["privacy_level"]), discoverable_disabled=payload["discoverable_disabled"], guild_scheduled_event_id=payload.get("guild_scheduled_event_id", undefined.UNDEFINED), ) diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index c0162b70b4..37e129e4f9 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -4392,7 +4392,6 @@ async def create_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: str, - privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, guild_scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] @@ -4402,7 +4401,6 @@ async def create_stage_instance( body = data_binding.JSONObjectBuilder() body.put_snowflake("channel_id", channel) body.put("topic", topic) - body.put("privacy_level", privacy_level) body.put("send_start_notification", send_start_notification) body.put_snowflake("guild_scheduled_event_id", guild_scheduled_event_id) @@ -4415,12 +4413,10 @@ async def edit_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: undefined.UndefinedOr[str] = undefined.UNDEFINED, - privacy_level: undefined.UndefinedOr[stage_instances.StagePrivacyLevel] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: route = routes.PATCH_STAGE_INSTANCE.compile(channel=channel) body = data_binding.JSONObjectBuilder() body.put("topic", topic) - body.put("privacy_level", privacy_level) response = await self._request(route, json=body) assert isinstance(response, dict) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index b9eccedc2b..f491c7597c 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -23,7 +23,7 @@ """Application and entities that are used to describe Stage instances on Discord.""" from __future__ import annotations -__all__: typing.List[str] = ["StagePrivacyLevel", "StageInstance"] +__all__: typing.Sequence[str] = ("StageInstance",) import typing @@ -41,17 +41,6 @@ from hikari import traits -@typing.final -class StagePrivacyLevel(int, enums.Enum): - """The privacy level of a Stage instance.""" - - PUBLIC = 1 - """The Stage instance is visible publicly. (Deprecated)""" - - GUILD = 2 - """The Stage instance is only visible to the guild members""" - - @attr.define(hash=True, kw_only=True, weakref_slot=False) class StageInstance(snowflakes.Unique): """Represents a Stage instance.""" @@ -73,9 +62,6 @@ class StageInstance(snowflakes.Unique): topic: str = attr.field(eq=False, hash=False, repr=False) """The topic of the Stage instance.""" - privacy_level: typing.Union[StagePrivacyLevel, int] = attr.field(eq=False, hash=False, repr=False) - """The privacy level of the Stage instance.""" - discoverable_disabled: bool = attr.field(eq=False, hash=False, repr=False) """Whether or not Stage discovery is disabled.""" diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 618bf9195a..3012ce7475 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -43,7 +43,6 @@ def stage_instance(self, mock_app): channel_id=snowflakes.Snowflake(6969), guild_id=snowflakes.Snowflake(420), topic="beanos", - privacy_level=stage_instances.StagePrivacyLevel.PUBLIC, discoverable_disabled=True, guild_scheduled_event_id=snowflakes.Snowflake(1337), ) @@ -63,9 +62,6 @@ def test_guild_id_property(self, stage_instance): def test_topic_property(self, stage_instance): assert stage_instance.topic == "beanos" - def test_privacy_level_property(self, stage_instance): - assert stage_instance.privacy_level == 1 - def test_discoverable_disabled_property(self, stage_instance): assert stage_instance.discoverable_disabled is True From f2e78d6aaa35357f8291ff51ea6253f1b3e85414 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:07:39 +0100 Subject: [PATCH 09/30] Lowercase *every instance of "stage instance" --- hikari/api/entity_factory.py | 2 +- hikari/api/event_factory.py | 12 ++++++------ hikari/api/rest.py | 34 +++++++++++++++++----------------- hikari/events/stage_events.py | 20 ++++++++++---------- hikari/stage_instances.py | 26 +++++++++++++------------- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index 230151760e..30ffd672ce 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -1997,7 +1997,7 @@ def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_model """ ######################### - # Stage instance models # + # STAGE INSTANCE MODELS # ######################### @abc.abstractmethod diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py index 608e8891de..cf97163926 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -1404,7 +1404,7 @@ def deserialize_voice_server_update_event( def deserialize_stage_instance_create_event( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> stage_events.StageInstanceCreateEvent: - """Parse a raw payload from Discord into a Stage instance create event object. + """Parse a raw payload from Discord into a stage instance create event object. Parameters ---------- @@ -1416,14 +1416,14 @@ def deserialize_stage_instance_create_event( Returns ------- hikari.events.voice_events.StageInstanceCreateEvent - The parsed Stage instance create event object. + The parsed stage instance create event object. """ @abc.abstractmethod def deserialize_stage_instance_edit_event( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> stage_events.StageInstanceEditEvent: - """Parse a raw payload from Discord into a Stage instance update event object. + """Parse a raw payload from Discord into a stage instance update event object. Parameters ---------- @@ -1435,14 +1435,14 @@ def deserialize_stage_instance_edit_event( Returns ------- hikari.events.voice_events.StageInstanceEditEvent - The parsed Stage instance update event object. + The parsed stage instance update event object. """ @abc.abstractmethod def deserialize_stage_instance_delete_event( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> stage_events.StageInstanceDeleteEvent: - """Parse a raw payload from Discord into a Stage instance delete event object. + """Parse a raw payload from Discord into a stage instance delete event object. Parameters ---------- @@ -1454,5 +1454,5 @@ def deserialize_stage_instance_delete_event( Returns ------- hikari.events.voice_events.StageInstanceDeleteEvent - The parsed Stage instance delete event object. + The parsed stage instance delete event object. """ diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 4ea2f449a6..d5ad8eb240 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8224,17 +8224,17 @@ def fetch_scheduled_event_users( async def fetch_stage_instance( self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] ) -> stage_instances.StageInstance: - """Fetch the Stage instance associated with a guild stage channel. + """Fetch the stage instance associated with a guild stage channel. Parameters ---------- channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] - The guild stage channel to fetch the Stage instance from. + The guild stage channel to fetch the stage instance from. Returns ------- typing.Optional[hikari.stage_instances.StageInstance] - The Stage instance associated with the guild stage channel. + The stage instance associated with the guild stage channel. `builtins.None` if no Stage instance exists in the stage channel. @@ -8270,29 +8270,29 @@ async def create_stage_instance( snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] ] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: - """Create a Stage instance in guild stage channel. + """Create a stage instance in guild stage channel. Parameters ---------- channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] - The channel to use for the Stage instance creation. + The channel to use for the stage instance creation. Other Parameters ---------------- topic: builtins.str - The topic for the Stage instance. + The topic for the stage instance. send_start_notification: hikari.undefined.UndefinedOr[builtins.bool] - Whether to notify @everyone that the Stage instance has started. + Whether to send a notification to *all* server members that the stage instance has started. guild_scheduled_event_id: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.scheduled_events.ScheduledEvent]] - The ID of the scheduled event to associate with the Stage instance. + The ID of the scheduled event to associate with the stage instance. Returns ------- hikari.stage_instances.StageInstance - The created Stage instance. + The created stage instance. Raises ------ @@ -8322,26 +8322,26 @@ async def edit_stage_instance( *, topic: undefined.UndefinedOr[str] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: - """Edit the Stage instance in a guild stage channel. + """Edit the stage instance in a guild stage channel. !!! note This will raise `hikari.errors.UnauthorizedError` if the bot is not a moderator - of the Stage instance. + of the stage instance. Parameters ---------- channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] - The channel that the Stage instance is associated with. + The channel that the stage instance is associated with. Other Parameters ---------------- topic: hikari.undefined.UndefinedOr[builtins.str] - The topic for the Stage instance. + The topic for the stage instance. Returns ------- hikari.stage_instances.StageInstance - The edited Stage instance. + The edited stage instance. Raises ------ @@ -8368,17 +8368,17 @@ async def edit_stage_instance( async def delete_stage_instance( self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] ) -> stage_instances.StageInstance: - """Delete the Stage instance. + """Delete the stage instance. Parameters ---------- channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] - The guild stage channel to fetch the Stage instance from. + The guild stage channel to fetch the stage instance from. Returns ------- hikari.stage_instances.StageInstance - The Stage instance that was deleted. + The stage instance that was deleted. Raises ------ diff --git a/hikari/events/stage_events.py b/hikari/events/stage_events.py index 467dd37175..9a4f84591b 100644 --- a/hikari/events/stage_events.py +++ b/hikari/events/stage_events.py @@ -20,7 +20,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Events that fire when Stage instances are created/updated/deleted.""" +"""Events that fire when stage instances are created/updated/deleted.""" from __future__ import annotations @@ -43,7 +43,7 @@ @base_events.requires_intents(intents.Intents.GUILDS) class StageInstanceEvent(shard_events.ShardEvent, abc.ABC): - """Event base for any event that involves Stage instances.""" + """Event base for any event that involves stage instances.""" __slots__: typing.Sequence[str] = () @@ -61,12 +61,12 @@ def stage_instance_id(self) -> snowflakes.Snowflake: @property @abc.abstractmethod def stage_instance(self) -> StageInstance: - """Stage Instance that this event relates to. + """Stage instance that this event relates to. Returns ------- hikari.stage_instance.StageInstance - The Stage Instance that this event relates to. + The stage instance that this event relates to. """ @@ -74,13 +74,13 @@ def stage_instance(self) -> StageInstance: @attr.define(kw_only=True, weakref_slot=False) @base_events.requires_intents(intents.Intents.GUILDS) class StageInstanceCreateEvent(StageInstanceEvent): - """Event fired when a Stage instance is created.""" + """Event fired when a stage instance is created.""" shard: gateway_shard.GatewayShard = attr.field(metadata={attrs_extensions.SKIP_DEEP_COPY: True}) # <>. stage_instance: StageInstance = attr.field() - """The Stage instance that was created.""" + """The stage instance that was created.""" @property def app(self) -> traits.RESTAware: @@ -97,13 +97,13 @@ def stage_instance_id(self) -> snowflakes.Snowflake: @attr.define(kw_only=True, weakref_slot=False) @base_events.requires_intents(intents.Intents.GUILDS) class StageInstanceEditEvent(StageInstanceEvent): - """Event fired when a Stage instance is edited.""" + """Event fired when a stage instance is edited.""" shard: gateway_shard.GatewayShard = attr.field(metadata={attrs_extensions.SKIP_DEEP_COPY: True}) # <>. stage_instance: StageInstance = attr.field() - """The Stage instance that was edited.""" + """The stage instance that was edited.""" @property def app(self) -> traits.RESTAware: @@ -119,13 +119,13 @@ def stage_instance_id(self) -> snowflakes.Snowflake: @attr.define(kw_only=True, weakref_slot=False) @base_events.requires_intents(intents.Intents.GUILDS) class StageInstanceDeleteEvent(StageInstanceEvent): - """Event fired when a Stage instance is deleted.""" + """Event fired when a stage instance is deleted.""" shard: gateway_shard.GatewayShard = attr.field(metadata={attrs_extensions.SKIP_DEEP_COPY: True}) # <>. stage_instance: StageInstance = attr.field() - """The Stage instance that was deleted.""" + """The stage instance that was deleted.""" @property def app(self) -> traits.RESTAware: diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index f491c7597c..5b96bc486e 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -20,7 +20,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""Application and entities that are used to describe Stage instances on Discord.""" +"""Application and entities that are used to describe stage instances on Discord.""" from __future__ import annotations __all__: typing.Sequence[str] = ("StageInstance",) @@ -43,10 +43,10 @@ @attr.define(hash=True, kw_only=True, weakref_slot=False) class StageInstance(snowflakes.Unique): - """Represents a Stage instance.""" + """Represents a stage instance.""" id: snowflakes.Snowflake = attr.field(eq=False, hash=False, repr=True) - """ID of the Stage instance.""" + """ID of the stage instance.""" app: traits.RESTAware = attr.field( repr=False, eq=False, hash=False, metadata={attrs_extensions.SKIP_DEEP_COPY: True} @@ -54,21 +54,21 @@ class StageInstance(snowflakes.Unique): """The client application that models may use for procedures.""" channel_id: snowflakes.Snowflake = attr.field(hash=True, repr=False) - """The channel ID of the Stage instance.""" + """The channel ID of the stage instance.""" guild_id: snowflakes.Snowflake = attr.field(hash=True, repr=False) - """The guild ID of the Stage instance.""" + """The guild ID of the stage instance.""" topic: str = attr.field(eq=False, hash=False, repr=False) - """The topic of the Stage instance.""" + """The topic of the stage instance.""" discoverable_disabled: bool = attr.field(eq=False, hash=False, repr=False) - """Whether or not Stage discovery is disabled.""" + """Whether or not stage discovery is disabled.""" guild_scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] ] = attr.field(eq=False, hash=False, repr=False) - "The ID of the scheduled event for this Stage instance, if it exists." + "The ID of the scheduled event for this stage instance, if it exists." def get_channel(self) -> typing.Optional[channels.GuildStageChannel]: """Return the guild stage channel where this stage instance was created. @@ -127,7 +127,7 @@ async def fetch_channel(self) -> channels.GuildStageChannel: return channel def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: - """Get the cached guild that this Stage Instance relates to, if known. + """Get the cached guild that this stage instance relates to, if known. If not known, this will return `builtins.None` instead. @@ -141,12 +141,12 @@ def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: return self.app.cache.get_guild(self.guild_id) async def fetch_guild(self) -> guilds.RESTGuild: - """Fetch the guild linked to this Stage Instance. + """Fetch the guild linked to this stage Instance. Returns ------- hikari.guilds.RESTGuild - The guild linked to this Stage Instance + The guild linked to this stage instance Raises ------ @@ -173,12 +173,12 @@ async def fetch_guild(self) -> guilds.RESTGuild: return await self.app.rest.fetch_guild(self.guild_id) async def fetch_scheduled_event(self) -> typing.Optional[scheduled_events.ScheduledEvent]: - """Fetch the scheduled event for this Stage Instance. + """Fetch the scheduled event for this stage instance. Returns ------- typing.Optional[hikari.scheduled_events.ScheduledEvent] - The scheduled event for this Stage Instance, if it exists. + The scheduled event for this stage instance, if it exists. Raises ------ From ff65fb679db79a031ed977c30c43022ed457c740 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:19:33 +0100 Subject: [PATCH 10/30] Triple quote docstring --- hikari/stage_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 5b96bc486e..08354b2f60 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -68,7 +68,7 @@ class StageInstance(snowflakes.Unique): guild_scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] ] = attr.field(eq=False, hash=False, repr=False) - "The ID of the scheduled event for this stage instance, if it exists." + """The ID of the scheduled event for this stage instance, if it exists.""" def get_channel(self) -> typing.Optional[channels.GuildStageChannel]: """Return the guild stage channel where this stage instance was created. From 36ce0b95d9147d075ce2a1422e82881f4eb70fd1 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:36:15 +0100 Subject: [PATCH 11/30] Cast guild_scheduled_event_id to snowflake --- hikari/impl/entity_factory.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index eaba415c23..45ed65a6e1 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1475,7 +1475,9 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ guild_id=snowflakes.Snowflake(payload["guild_id"]), topic=payload["topic"], discoverable_disabled=payload["discoverable_disabled"], - guild_scheduled_event_id=payload.get("guild_scheduled_event_id", undefined.UNDEFINED), + guild_scheduled_event_id=snowflakes.Snowflake(payload["guild_scheduled_event_id"]) + if "guild_scheduled_event_id" in payload + else undefined.UNDEFINED, ) def deserialize_channel( From b8ec2c18daa53fbf8e1ff87300d8275b6a52f347 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:39:55 +0100 Subject: [PATCH 12/30] Move note about UnauthorizedError to the right place --- hikari/api/rest.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index d5ad8eb240..ffc5cb9bc7 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8324,10 +8324,6 @@ async def edit_stage_instance( ) -> stage_instances.StageInstance: """Edit the stage instance in a guild stage channel. - !!! note - This will raise `hikari.errors.UnauthorizedError` if the bot is not a moderator - of the stage instance. - Parameters ---------- channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] @@ -8346,7 +8342,8 @@ async def edit_stage_instance( Raises ------ hikari.errors.UnauthorizedError - If you are unauthorized to make the request (invalid/missing token). + If you are unauthorized to make the request (invalid/missing token + or you are not a moderator of the stage instance). hikari.errors.NotFoundError If the interaction or response is not found. hikari.errors.RateLimitTooLongError From d4f114ad768b5d8cb0b9f777436d80a6851dc862 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:53:45 +0100 Subject: [PATCH 13/30] Turns out g_s_e_id is always in the payload, just set as None --- hikari/impl/entity_factory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 45ed65a6e1..6d794345a1 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1476,7 +1476,7 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ topic=payload["topic"], discoverable_disabled=payload["discoverable_disabled"], guild_scheduled_event_id=snowflakes.Snowflake(payload["guild_scheduled_event_id"]) - if "guild_scheduled_event_id" in payload + if payload["guild_scheduled_event_id"] else undefined.UNDEFINED, ) From ef7ed23b1902fd296f6291bf0f77423285527a36 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:54:09 +0100 Subject: [PATCH 14/30] Correct info/raised exc about non-existent stage instance --- hikari/api/rest.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index ffc5cb9bc7..db6a810a31 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8233,17 +8233,15 @@ async def fetch_stage_instance( Returns ------- - typing.Optional[hikari.stage_instances.StageInstance] + hikari.stage_instances.StageInstance The stage instance associated with the guild stage channel. - `builtins.None` if no Stage instance exists in the stage channel. - Raises ------ hikari.errors.UnauthorizedError If you are unauthorized to make the request (invalid/missing token). hikari.errors.NotFoundError - If the interaction or response is not found. + If the stage instance or channel is not found. hikari.errors.RateLimitTooLongError Raised in the event that a rate limit occurs that is longer than `max_rate_limit` when making a request. From 7aed3d391ac4b593072f648701fe53b3d052f0e9 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 09:58:12 +0100 Subject: [PATCH 15/30] Remove unused imports, black changes --- hikari/stage_instances.py | 1 - tests/hikari/events/test_stage_events.py | 4 +--- tests/hikari/test_stage_instances.py | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 08354b2f60..cf23f57e0f 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -34,7 +34,6 @@ from hikari import snowflakes from hikari import undefined from hikari.internal import attrs_extensions -from hikari.internal import enums if typing.TYPE_CHECKING: from hikari import guilds diff --git a/tests/hikari/events/test_stage_events.py b/tests/hikari/events/test_stage_events.py index 18b5be60fe..58538e1101 100644 --- a/tests/hikari/events/test_stage_events.py +++ b/tests/hikari/events/test_stage_events.py @@ -30,9 +30,7 @@ class TestStageInstanceCreateEvent: @pytest.fixture() def event(self): - return stage_events.StageInstanceCreateEvent( - shard=object(), stage_instance=mock.Mock() - ) + return stage_events.StageInstanceCreateEvent(shard=object(), stage_instance=mock.Mock()) def test_app_property(self, event): assert event.app is event.stage_instance.app diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 3012ce7475..286da289c1 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -26,7 +26,6 @@ from hikari import channels from hikari import snowflakes from hikari import stage_instances -from hikari.impl import gateway_bot as bot @pytest.fixture() From 3bfaf63c21dfca7d0d45ecac187aa767bbd89e21 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 10:15:27 +0100 Subject: [PATCH 16/30] Fix failing tests, remove remaining refs to deprecated attribute --- tests/hikari/impl/test_entity_factory.py | 3 +-- tests/hikari/impl/test_rest.py | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py index fa902967b6..04ef22df32 100644 --- a/tests/hikari/impl/test_entity_factory.py +++ b/tests/hikari/impl/test_entity_factory.py @@ -7055,7 +7055,7 @@ def stage_instance_payload(self): "guild_id": "197038439483310086", "channel_id": "733488538393510049", "topic": "Testing Testing, 123", - "privacy_level": 1, + "guild_scheduled_event_id": "363820363920323120", "discoverable_disabled": False, } @@ -7067,5 +7067,4 @@ def test_deserialize_stage_instance(self, entity_factory_impl, stage_instance_pa assert stage_instance.channel_id == 733488538393510049 assert stage_instance.guild_id == 197038439483310086 assert stage_instance.topic == "Testing Testing, 123" - assert stage_instance.privacy_level == 1 assert stage_instance.discoverable_disabled is False diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py index 3ccb142685..0b0b2b242e 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -6426,18 +6426,20 @@ async def test_fetch_stage_instance(self, rest_client): async def test_create_stage_instance(self, rest_client): expected_route = routes.POST_STAGE_INSTANCE.compile() - expected_json = {"channel_id": "7334", "privacy_level": 1, "topic": "ur mom"} + expected_json = {"channel_id": "7334", "topic": "ur mom", "guild_scheduled_event_id": "3361203239"} mock_payload = { "id": "8406", "guild_id": "19703", "channel_id": "7334", "topic": "ur mom", - "privacy_level": 1, + "guild_scheduled_event_id": "3361203239", "discoverable_disabled": False, } rest_client._request = mock.AsyncMock(return_value=mock_payload) - result = await rest_client.create_stage_instance(channel=7334, privacy_level=1, topic="ur mom") + result = await rest_client.create_stage_instance( + channel=7334, topic="ur mom", guild_scheduled_event_id=3361203239 + ) assert result is rest_client._entity_factory.deserialize_stage_instance.return_value rest_client._request.assert_called_once_with(expected_route, json=expected_json) @@ -6445,18 +6447,17 @@ async def test_create_stage_instance(self, rest_client): async def test_edit_stage_instance(self, rest_client): expected_route = routes.PATCH_STAGE_INSTANCE.compile(channel=7334) - expected_json = {"privacy_level": 1, "topic": "ur mom"} + expected_json = {"topic": "ur mom"} mock_payload = { "id": "8406", "guild_id": "19703", "channel_id": "7334", "topic": "ur mom", - "privacy_level": 1, "discoverable_disabled": False, } rest_client._request = mock.AsyncMock(return_value=mock_payload) - result = await rest_client.edit_stage_instance(channel=7334, privacy_level=1, topic="ur mom") + result = await rest_client.edit_stage_instance(channel=7334, topic="ur mom") assert result is rest_client._entity_factory.deserialize_stage_instance.return_value rest_client._request.assert_called_once_with(expected_route, json=expected_json) @@ -6469,7 +6470,6 @@ async def test_delete_stage_instance(self, rest_client): "guild_id": "19703", "channel_id": "7334", "topic": "ur mom", - "privacy_level": 1, "discoverable_disabled": False, } rest_client._request = mock.AsyncMock(return_value=mock_payload) From e666ed5dd26d6394ec108a00bd805f2e530be0c3 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 10:20:55 +0100 Subject: [PATCH 17/30] guild_scheduled_event_id should be None instead of UNDEFINED --- hikari/impl/entity_factory.py | 2 +- hikari/stage_instances.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 6d794345a1..956aab3c22 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1477,7 +1477,7 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ discoverable_disabled=payload["discoverable_disabled"], guild_scheduled_event_id=snowflakes.Snowflake(payload["guild_scheduled_event_id"]) if payload["guild_scheduled_event_id"] - else undefined.UNDEFINED, + else None, ) def deserialize_channel( diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index cf23f57e0f..4b2fe26a26 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -64,9 +64,9 @@ class StageInstance(snowflakes.Unique): discoverable_disabled: bool = attr.field(eq=False, hash=False, repr=False) """Whether or not stage discovery is disabled.""" - guild_scheduled_event_id: undefined.UndefinedOr[ - snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] - ] = attr.field(eq=False, hash=False, repr=False) + guild_scheduled_event_id: typing.Optional[snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent]] = attr.field( + eq=False, hash=False, repr=False + ) """The ID of the scheduled event for this stage instance, if it exists.""" def get_channel(self) -> typing.Optional[channels.GuildStageChannel]: @@ -201,7 +201,7 @@ async def fetch_scheduled_event(self) -> typing.Optional[scheduled_events.Schedu hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. """ - if self.guild_scheduled_event_id is undefined.UNDEFINED: + if not self.guild_scheduled_event_id: return None return await self.app.rest.fetch_scheduled_event(self.guild_id, self.guild_scheduled_event_id) From 594c3a69653b7dd96c6f41725deb8181a033915f Mon Sep 17 00:00:00 2001 From: nulldomain Date: Wed, 4 Oct 2023 10:29:52 +0100 Subject: [PATCH 18/30] Remove unused import --- hikari/stage_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 4b2fe26a26..0f32d284d2 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -32,7 +32,6 @@ from hikari import channels from hikari import scheduled_events from hikari import snowflakes -from hikari import undefined from hikari.internal import attrs_extensions if typing.TYPE_CHECKING: From cbd64c99e28fdd2c18dae3d442a1551846b20b4e Mon Sep 17 00:00:00 2001 From: davfsa Date: Sat, 28 Oct 2023 08:47:06 +0200 Subject: [PATCH 19/30] Extract guild_scheduled_event_id to its own variable --- hikari/impl/entity_factory.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 956aab3c22..dec7abdd49 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1468,6 +1468,10 @@ def deserialize_guild_private_thread( ) def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_instances.StageInstance: + guild_scheduled_event_id = ( + snowflakes.Snowflake(payload["guild_scheduled_event_id"]) if payload["guild_scheduled_event_id"] else None + ) + return stage_instances.StageInstance( app=self._app, id=snowflakes.Snowflake(payload["id"]), @@ -1475,9 +1479,7 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ guild_id=snowflakes.Snowflake(payload["guild_id"]), topic=payload["topic"], discoverable_disabled=payload["discoverable_disabled"], - guild_scheduled_event_id=snowflakes.Snowflake(payload["guild_scheduled_event_id"]) - if payload["guild_scheduled_event_id"] - else None, + guild_scheduled_event_id=guild_scheduled_event_id, ) def deserialize_channel( From e2f4b433014d30fbbae1f2ee58e686955382b22c Mon Sep 17 00:00:00 2001 From: nulldomain Date: Thu, 2 Nov 2023 09:21:35 +0000 Subject: [PATCH 20/30] Add missing tests --- hikari/stage_instances.py | 2 +- tests/hikari/test_stage_instances.py | 33 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 0f32d284d2..d8caf52c63 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -32,11 +32,11 @@ from hikari import channels from hikari import scheduled_events from hikari import snowflakes +from hikari import traits from hikari.internal import attrs_extensions if typing.TYPE_CHECKING: from hikari import guilds - from hikari import traits @attr.define(hash=True, kw_only=True, weakref_slot=False) diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 286da289c1..078a9b2b25 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -67,6 +67,18 @@ def test_discoverable_disabled_property(self, stage_instance): def test_guild_scheduled_event_id_property(self, stage_instance): assert stage_instance.guild_scheduled_event_id == 1337 + def test_get_channel(self, stage_instance): + mock_stage_channel = mock.Mock(channels.GuildStageChannel, channel_id=snowflakes.Snowflake(6969)) + stage_instance.app.cache.get_guild_channel = mock.Mock(return_value=mock_stage_channel) + + assert stage_instance.get_channel() == mock_stage_channel + stage_instance.app.cache.get_guild_channel.assert_called_once_with(6969) + + def test_get_channel_when_no_cache_trait(self, stage_instance): + stage_instance.app = object() + + assert stage_instance.get_channel() is None + @pytest.mark.asyncio() async def test_fetch_channel(self, stage_instance): mock_stage_channel = mock.Mock(channels.GuildStageChannel) @@ -75,9 +87,30 @@ async def test_fetch_channel(self, stage_instance): assert await stage_instance.fetch_channel() == mock_stage_channel stage_instance.app.rest.fetch_channel.assert_awaited_once_with(6969) + def test_get_guild(self, stage_instance): + mock_guild = mock.Mock(channels.GuildStageChannel, guild_id=420) + stage_instance.app.cache.get_guild = mock.Mock(return_value=mock_guild) + + assert stage_instance.get_guild() == mock_guild + stage_instance.app.cache.get_guild.assert_called_once_with(420) + + def test_get_guild_when_no_cache_trait(self, stage_instance): + stage_instance.app = object() + + assert stage_instance.get_guild() is None + @pytest.mark.asyncio() async def test_fetch_guild(self, stage_instance): stage_instance.app.rest.fetch_guild = mock.AsyncMock() assert await stage_instance.fetch_guild() == stage_instance.app.rest.fetch_guild.return_value stage_instance.app.rest.fetch_guild.assert_awaited_once_with(420) + + @pytest.mark.asyncio() + async def test_fetch_scheduled_event(self, stage_instance): + stage_instance.app.rest.fetch_scheduled_event = mock.AsyncMock() + + f_s_e = await stage_instance.fetch_scheduled_event() + + assert f_s_e == stage_instance.app.rest.fetch_scheduled_event.return_value + stage_instance.app.rest.fetch_scheduled_event.assert_awaited_once_with(420, 1337) From a6dd6303a00150ee2633f1d0c2bd37b4f87be0ad Mon Sep 17 00:00:00 2001 From: nulldomain Date: Thu, 7 Dec 2023 08:57:17 +0000 Subject: [PATCH 21/30] Update hikari/internal/routes.py Co-authored-by: davfsa Signed-off-by: nulldomain --- hikari/internal/routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py index 343f7b696c..689732cbe6 100644 --- a/hikari/internal/routes.py +++ b/hikari/internal/routes.py @@ -340,6 +340,7 @@ def compile_to_file( POST_CHANNEL_WEBHOOKS: typing.Final[Route] = Route(POST, "/channels/{channel}/webhooks") GET_CHANNEL_WEBHOOKS: typing.Final[Route] = Route(GET, "/channels/{channel}/webhooks") +# Stage instances POST_STAGE_INSTANCE: typing.Final[Route] = Route(POST, "/stage-instances") GET_STAGE_INSTANCE: typing.Final[Route] = Route(GET, "/stage-instances/{channel}") PATCH_STAGE_INSTANCE: typing.Final[Route] = Route(PATCH, "/stage-instances/{channel}") From 0358720c9939b0b83cd24c966c2f250fe72467e7 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Thu, 7 Dec 2023 08:57:37 +0000 Subject: [PATCH 22/30] Update hikari/api/rest.py Co-authored-by: davfsa Signed-off-by: nulldomain --- hikari/api/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index db6a810a31..3fe19d4c71 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8329,7 +8329,7 @@ async def edit_stage_instance( Other Parameters ---------------- - topic: hikari.undefined.UndefinedOr[builtins.str] + topic: hikari.undefined.UndefinedOr[str] The topic for the stage instance. Returns From 144f93dff8101e856f9424441bc5878dfc50b171 Mon Sep 17 00:00:00 2001 From: nulldomain Date: Thu, 7 Dec 2023 08:57:49 +0000 Subject: [PATCH 23/30] Update hikari/api/rest.py Co-authored-by: davfsa Signed-off-by: nulldomain --- hikari/api/rest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 3fe19d4c71..fa08559810 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8277,10 +8277,10 @@ async def create_stage_instance( Other Parameters ---------------- - topic: builtins.str + topic: str The topic for the stage instance. - send_start_notification: hikari.undefined.UndefinedOr[builtins.bool] + send_start_notification: hikari.undefined.UndefinedOr[bool] Whether to send a notification to *all* server members that the stage instance has started. guild_scheduled_event_id: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.scheduled_events.ScheduledEvent]] From 8429ded7213c28ba1db0b6d09009a599f3b2d1c5 Mon Sep 17 00:00:00 2001 From: tandemdude <43570299+tandemdude@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:32:10 +0100 Subject: [PATCH 24/30] Address PR comments and fix flake8 errors --- hikari/api/event_factory.py | 18 ++++---- hikari/events/stage_events.py | 59 ++++++++---------------- hikari/impl/entity_factory.py | 5 +- hikari/impl/event_factory.py | 6 +-- hikari/impl/event_manager.py | 7 ++- hikari/stage_instances.py | 4 +- tests/hikari/events/test_stage_events.py | 10 ++-- tests/hikari/impl/test_entity_factory.py | 2 +- tests/hikari/impl/test_event_factory.py | 6 +-- tests/hikari/impl/test_event_manager.py | 10 ++-- tests/hikari/test_stage_instances.py | 10 ++-- 11 files changed, 58 insertions(+), 79 deletions(-) diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py index 8af8977ef0..b402a5c476 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -1422,9 +1422,9 @@ def deserialize_stage_instance_create_event( Parameters ---------- - shard : hikari.api.shard.GatewayShard + shard The shard that emitted this event. - payload : hikari.internal.data_binding.JSONObject + payload The dict payload to parse. Returns @@ -1434,21 +1434,21 @@ def deserialize_stage_instance_create_event( """ @abc.abstractmethod - def deserialize_stage_instance_edit_event( + def deserialize_stage_instance_update_event( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject - ) -> stage_events.StageInstanceEditEvent: + ) -> stage_events.StageInstanceUpdateEvent: """Parse a raw payload from Discord into a stage instance update event object. Parameters ---------- - shard : hikari.api.shard.GatewayShard + shard The shard that emitted this event. - payload : hikari.internal.data_binding.JSONObject + payload The dict payload to parse. Returns ------- - hikari.events.voice_events.StageInstanceEditEvent + hikari.events.voice_events.StageInstanceUpdateEvent The parsed stage instance update event object. """ @@ -1460,9 +1460,9 @@ def deserialize_stage_instance_delete_event( Parameters ---------- - shard : hikari.api.shard.GatewayShard + shard The shard that emitted this event. - payload : hikari.internal.data_binding.JSONObject + payload The dict payload to parse. Returns diff --git a/hikari/events/stage_events.py b/hikari/events/stage_events.py index 9a4f84591b..002c006cef 100644 --- a/hikari/events/stage_events.py +++ b/hikari/events/stage_events.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # cython: language_level=3 # Copyright (c) 2020 Nekokatt -# Copyright (c) 2021 davfsa +# Copyright (c) 2021-present davfsa # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -48,15 +48,9 @@ class StageInstanceEvent(shard_events.ShardEvent, abc.ABC): __slots__: typing.Sequence[str] = () @property - @abc.abstractmethod - def stage_instance_id(self) -> snowflakes.Snowflake: - """ID of the stage instance that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the stage instance that this event relates to. - """ + def app(self) -> traits.RESTAware: + # <>. + return self.stage_instance.app @property @abc.abstractmethod @@ -69,6 +63,17 @@ def stage_instance(self) -> StageInstance: The stage instance that this event relates to. """ + @property + def stage_instance_id(self) -> snowflakes.Snowflake: + """ID of the stage instance that this event relates to. + + Returns + ------- + hikari.snowflakes.Snowflake + The ID of the stage instance that this event relates to. + """ + return self.stage_instance.id + @attrs_extensions.with_copy @attr.define(kw_only=True, weakref_slot=False) @@ -82,37 +87,18 @@ class StageInstanceCreateEvent(StageInstanceEvent): stage_instance: StageInstance = attr.field() """The stage instance that was created.""" - @property - def app(self) -> traits.RESTAware: - # <>. - return self.stage_instance.app - - @property - def stage_instance_id(self) -> snowflakes.Snowflake: - # <>. - return self.stage_instance.id - @attrs_extensions.with_copy @attr.define(kw_only=True, weakref_slot=False) @base_events.requires_intents(intents.Intents.GUILDS) -class StageInstanceEditEvent(StageInstanceEvent): - """Event fired when a stage instance is edited.""" +class StageInstanceUpdateEvent(StageInstanceEvent): + """Event fired when a stage instance is updated.""" shard: gateway_shard.GatewayShard = attr.field(metadata={attrs_extensions.SKIP_DEEP_COPY: True}) # <>. stage_instance: StageInstance = attr.field() - """The stage instance that was edited.""" - - @property - def app(self) -> traits.RESTAware: - # <>. - return self.stage_instance.app - - @property - def stage_instance_id(self) -> snowflakes.Snowflake: - return self.stage_instance.id + """The stage instance that was updated.""" @attrs_extensions.with_copy @@ -126,12 +112,3 @@ class StageInstanceDeleteEvent(StageInstanceEvent): stage_instance: StageInstance = attr.field() """The stage instance that was deleted.""" - - @property - def app(self) -> traits.RESTAware: - # <>. - return self.stage_instance.app - - @property - def stage_instance_id(self) -> snowflakes.Snowflake: - return self.stage_instance.id diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index 11f0b17b43..f41e415d6b 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1472,9 +1472,8 @@ def deserialize_guild_private_thread( ) def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_instances.StageInstance: - guild_scheduled_event_id = ( - snowflakes.Snowflake(payload["guild_scheduled_event_id"]) if payload["guild_scheduled_event_id"] else None - ) + raw_event_id = payload["guild_scheduled_event_id"] + guild_scheduled_event_id = snowflakes.Snowflake(raw_event_id) if raw_event_id else None return stage_instances.StageInstance( app=self._app, diff --git a/hikari/impl/event_factory.py b/hikari/impl/event_factory.py index 5e521f1d1f..be264a74d0 100644 --- a/hikari/impl/event_factory.py +++ b/hikari/impl/event_factory.py @@ -967,10 +967,10 @@ def deserialize_stage_instance_create_event( shard=shard, stage_instance=self._app.entity_factory.deserialize_stage_instance(payload) ) - def deserialize_stage_instance_edit_event( + def deserialize_stage_instance_update_event( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject - ) -> stage_events.StageInstanceEditEvent: - return stage_events.StageInstanceEditEvent( + ) -> stage_events.StageInstanceUpdateEvent: + return stage_events.StageInstanceUpdateEvent( shard=shard, stage_instance=self._app.entity_factory.deserialize_stage_instance(payload) ) diff --git a/hikari/impl/event_manager.py b/hikari/impl/event_manager.py index 455e1570cf..395046764f 100644 --- a/hikari/impl/event_manager.py +++ b/hikari/impl/event_manager.py @@ -48,6 +48,7 @@ from hikari.events import role_events from hikari.events import scheduled_events from hikari.events import shard_events +from hikari.events import stage_events from hikari.events import typing_events from hikari.events import user_events from hikari.events import voice_events @@ -889,18 +890,20 @@ async def on_entitlement_update(self, shard: gateway_shard.GatewayShard, payload """See https://discord.com/developers/docs/topics/gateway-events#entitlement-update for more info.""" await self.dispatch(self._event_factory.deserialize_entitlement_update_event(shard, payload)) + @event_manager_base.filtered(stage_events.StageInstanceCreateEvent) async def on_stage_instance_create( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> None: await self.dispatch(self._event_factory.deserialize_stage_instance_create_event(shard, payload)) + @event_manager_base.filtered(stage_events.StageInstanceUpdateEvent) async def on_stage_instance_update( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> None: - await self.dispatch(self._event_factory.deserialize_stage_instance_edit_event(shard, payload)) + await self.dispatch(self._event_factory.deserialize_stage_instance_update_event(shard, payload)) + @event_manager_base.filtered(stage_events.StageInstanceDeleteEvent) async def on_stage_instance_delete( self, shard: gateway_shard.GatewayShard, payload: data_binding.JSONObject ) -> None: await self.dispatch(self._event_factory.deserialize_stage_instance_delete_event(shard, payload)) -# TODO diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index d8caf52c63..db449a1dbd 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # cython: language_level=3 # Copyright (c) 2020 Nekokatt -# Copyright (c) 2021 davfsa +# Copyright (c) 2021-present davfsa # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -75,7 +75,7 @@ def get_channel(self) -> typing.Optional[channels.GuildStageChannel]: Returns ------- - hikari.channels.GuildStageChannel + typing.Optional[hikari.channels.GuildStageChannel] The guild stage channel where this stage instance was created. """ if not isinstance(self.app, traits.CacheAware): diff --git a/tests/hikari/events/test_stage_events.py b/tests/hikari/events/test_stage_events.py index 58538e1101..02aafe543c 100644 --- a/tests/hikari/events/test_stage_events.py +++ b/tests/hikari/events/test_stage_events.py @@ -28,7 +28,7 @@ class TestStageInstanceCreateEvent: - @pytest.fixture() + @pytest.fixture def event(self): return stage_events.StageInstanceCreateEvent(shard=object(), stage_instance=mock.Mock()) @@ -40,10 +40,10 @@ def test_stage_instance_id_property(self, event): assert event.stage_instance_id == 1234 -class TestStageInstanceEditEvent: - @pytest.fixture() +class TestStageInstanceUpdateEvent: + @pytest.fixture def event(self): - return stage_events.StageInstanceEditEvent( + return stage_events.StageInstanceUpdateEvent( shard=object(), stage_instance=mock.Mock(stage_instances.StageInstance) ) @@ -56,7 +56,7 @@ def test_stage_instance_id_property(self, event): class TestStageInstanceDeleteEvent: - @pytest.fixture() + @pytest.fixture def event(self): return stage_events.StageInstanceDeleteEvent( shard=object(), stage_instance=mock.Mock(stage_instances.StageInstance) diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py index 6168fd1c14..83466438f1 100644 --- a/tests/hikari/impl/test_entity_factory.py +++ b/tests/hikari/impl/test_entity_factory.py @@ -7215,7 +7215,7 @@ def test_deserialize_sku(self, entity_factory_impl, sku_payload): # Stage instance models # ######################### - @pytest.fixture() + @pytest.fixture def stage_instance_payload(self): return { "id": "840647391636226060", diff --git a/tests/hikari/impl/test_event_factory.py b/tests/hikari/impl/test_event_factory.py index 18025b1ad8..1be7fb48f6 100644 --- a/tests/hikari/impl/test_event_factory.py +++ b/tests/hikari/impl/test_event_factory.py @@ -1540,7 +1540,7 @@ def test_deserialize_stage_instance_create_event(self, event_factory, mock_app, assert event.stage_instance_id == mock_app.entity_factory.deserialize_stage_instance.return_value.id assert event.stage_instance == mock_app.entity_factory.deserialize_stage_instance.return_value - def test_deserialize_stage_instance_edit_event(self, event_factory, mock_app, mock_shard): + def test_deserialize_stage_instance_update_event(self, event_factory, mock_app, mock_shard): mock_payload = { "id": "840647391636226060", "guild_id": "197038439483310086", @@ -1549,8 +1549,8 @@ def test_deserialize_stage_instance_edit_event(self, event_factory, mock_app, mo "privacy_level": 2, "discoverable_disabled": True, } - event = event_factory.deserialize_stage_instance_edit_event(mock_shard, mock_payload) - assert isinstance(event, stage_events.StageInstanceEditEvent) + event = event_factory.deserialize_stage_instance_update_event(mock_shard, mock_payload) + assert isinstance(event, stage_events.StageInstanceUpdateEvent) assert event.shard is mock_shard assert event.app is event.stage_instance.app diff --git a/tests/hikari/impl/test_event_manager.py b/tests/hikari/impl/test_event_manager.py index 45e2a3b527..2d331e8b0d 100644 --- a/tests/hikari/impl/test_event_manager.py +++ b/tests/hikari/impl/test_event_manager.py @@ -1693,7 +1693,7 @@ async def test_on_guild_audit_log_entry_create( event_factory.deserialize_audit_log_entry_create_event.return_value ) - @pytest.mark.asyncio() + @pytest.mark.asyncio async def test_on_stage_instance_create( self, event_manager_impl: event_manager.EventManagerImpl, @@ -1716,7 +1716,7 @@ async def test_on_stage_instance_create( event_factory.deserialize_stage_instance_create_event.return_value ) - @pytest.mark.asyncio() + @pytest.mark.asyncio async def test_on_stage_instance_update( self, event_manager_impl: event_manager.EventManagerImpl, @@ -1734,12 +1734,12 @@ async def test_on_stage_instance_update( await event_manager_impl.on_stage_instance_update(shard, payload) - event_factory.deserialize_stage_instance_edit_event.assert_called_once_with(shard, payload) + event_factory.deserialize_stage_instance_update_event.assert_called_once_with(shard, payload) event_manager_impl.dispatch.assert_awaited_once_with( - event_factory.deserialize_stage_instance_edit_event.return_value + event_factory.deserialize_stage_instance_update_event.return_value ) - @pytest.mark.asyncio() + @pytest.mark.asyncio async def test_on_stage_instance_delete( self, event_manager_impl: event_manager.EventManagerImpl, diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 078a9b2b25..06ed7d5686 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -28,13 +28,13 @@ from hikari import stage_instances -@pytest.fixture() +@pytest.fixture def mock_app(): return mock.Mock() class TestStageInstance: - @pytest.fixture() + @pytest.fixture def stage_instance(self, mock_app): return stage_instances.StageInstance( app=mock_app, @@ -79,7 +79,7 @@ def test_get_channel_when_no_cache_trait(self, stage_instance): assert stage_instance.get_channel() is None - @pytest.mark.asyncio() + @pytest.mark.asyncio async def test_fetch_channel(self, stage_instance): mock_stage_channel = mock.Mock(channels.GuildStageChannel) stage_instance.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_stage_channel) @@ -99,14 +99,14 @@ def test_get_guild_when_no_cache_trait(self, stage_instance): assert stage_instance.get_guild() is None - @pytest.mark.asyncio() + @pytest.mark.asyncio async def test_fetch_guild(self, stage_instance): stage_instance.app.rest.fetch_guild = mock.AsyncMock() assert await stage_instance.fetch_guild() == stage_instance.app.rest.fetch_guild.return_value stage_instance.app.rest.fetch_guild.assert_awaited_once_with(420) - @pytest.mark.asyncio() + @pytest.mark.asyncio async def test_fetch_scheduled_event(self, stage_instance): stage_instance.app.rest.fetch_scheduled_event = mock.AsyncMock() From 5bc60212b7c7cefbd338e1ae7891a86a6f83afe8 Mon Sep 17 00:00:00 2001 From: "thomm.o" <43570299+tandemdude@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:18:40 +0100 Subject: [PATCH 25/30] Update hikari/stage_instances.py Co-authored-by: davfsa Signed-off-by: thomm.o <43570299+tandemdude@users.noreply.github.com> --- hikari/stage_instances.py | 136 -------------------------------------- 1 file changed, 136 deletions(-) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index db449a1dbd..5e38a0a716 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -68,139 +68,3 @@ class StageInstance(snowflakes.Unique): ) """The ID of the scheduled event for this stage instance, if it exists.""" - def get_channel(self) -> typing.Optional[channels.GuildStageChannel]: - """Return the guild stage channel where this stage instance was created. - - This will be empty if the channels are missing from the cache. - - Returns - ------- - typing.Optional[hikari.channels.GuildStageChannel] - The guild stage channel where this stage instance was created. - """ - if not isinstance(self.app, traits.CacheAware): - return None - - channel = self.app.cache.get_guild_channel(self.channel_id) - assert isinstance(channel, channels.GuildStageChannel) - - return channel - - async def fetch_channel(self) -> channels.GuildStageChannel: - """Fetch the stage channel where this stage instance was created. - - Returns - ------- - hikari.channels.GuildStageChannel - The stage channel where this stage instance was created. - - Raises - ------ - hikari.errors.BadRequestError - If any invalid snowflake IDs are passed; a snowflake may be invalid - due to it being outside of the range of a 64 bit integer. - hikari.errors.ForbiddenError - If you don't have access to the channel this message belongs to. - hikari.errors.NotFoundError - If the channel this message was created in does not exist. - 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.RateLimitedError - Usually, Hikari will handle and retry on hitting - rate-limits automatically. This includes most bucket-specific - rate-limits and global rate-limits. In some rare edge cases, - however, Discord implements other undocumented rules for - rate-limiting, such as limits per attribute. These cannot be - detected or handled normally by Hikari due to their undocumented - nature, and will trigger this exception if they occur. - hikari.errors.InternalServerError - If an internal error occurs on Discord while handling the request. - """ - channel = await self.app.rest.fetch_channel(self.channel_id) - assert isinstance(channel, channels.GuildStageChannel) - - return channel - - def get_guild(self) -> typing.Optional[guilds.GatewayGuild]: - """Get the cached guild that this stage instance relates to, if known. - - If not known, this will return `builtins.None` instead. - - Returns - ------- - typing.Optional[hikari.guilds.GatewayGuild] - The guild this event relates to, or `builtins.None` if not known. - """ - if not isinstance(self.app, traits.CacheAware): - return None - return self.app.cache.get_guild(self.guild_id) - - async def fetch_guild(self) -> guilds.RESTGuild: - """Fetch the guild linked to this stage Instance. - - Returns - ------- - hikari.guilds.RESTGuild - The guild linked to this stage instance - - Raises - ------ - hikari.errors.ForbiddenError - If you are not part of the guild. - 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.RateLimitedError - Usually, Hikari will handle and retry on hitting - rate-limits automatically. This includes most bucket-specific - rate-limits and global rate-limits. In some rare edge cases, - however, Discord implements other undocumented rules for - rate-limiting, such as limits per attribute. These cannot be - detected or handled normally by Hikari due to their undocumented - nature, and will trigger this exception if they occur. - hikari.errors.InternalServerError - If an internal error occurs on Discord while handling the request. - """ - return await self.app.rest.fetch_guild(self.guild_id) - - async def fetch_scheduled_event(self) -> typing.Optional[scheduled_events.ScheduledEvent]: - """Fetch the scheduled event for this stage instance. - - Returns - ------- - typing.Optional[hikari.scheduled_events.ScheduledEvent] - The scheduled event for this stage instance, if it exists. - - Raises - ------ - hikari.errors.ForbiddenError - If you are not part of the guild. - 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.RateLimitedError - Usually, Hikari will handle and retry on hitting - rate-limits automatically. This includes most bucket-specific - rate-limits and global rate-limits. In some rare edge cases, - however, Discord implements other undocumented rules for - rate-limiting, such as limits per attribute. These cannot be - detected or handled normally by Hikari due to their undocumented - nature, and will trigger this exception if they occur. - hikari.errors.InternalServerError - If an internal error occurs on Discord while handling the request. - """ - if not self.guild_scheduled_event_id: - return None - - return await self.app.rest.fetch_scheduled_event(self.guild_id, self.guild_scheduled_event_id) From 1d711796bec9b28ba57a35ac3155b3b12ea91241 Mon Sep 17 00:00:00 2001 From: "thomm.o" <43570299+tandemdude@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:20:09 +0100 Subject: [PATCH 26/30] Apply suggestions from code review Co-authored-by: davfsa Signed-off-by: thomm.o <43570299+tandemdude@users.noreply.github.com> --- hikari/api/entity_factory.py | 2 +- hikari/api/event_factory.py | 6 +++--- hikari/api/rest.py | 24 ++++++++---------------- hikari/events/stage_events.py | 16 ++-------------- 4 files changed, 14 insertions(+), 34 deletions(-) diff --git a/hikari/api/entity_factory.py b/hikari/api/entity_factory.py index f483202477..9b84f68648 100644 --- a/hikari/api/entity_factory.py +++ b/hikari/api/entity_factory.py @@ -1984,6 +1984,6 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ Returns ------- - hikari.channels.StageInstance + hikari.stage_intances.StageInstance The deserialized stage instance object """ diff --git a/hikari/api/event_factory.py b/hikari/api/event_factory.py index b402a5c476..6996c22d6d 100644 --- a/hikari/api/event_factory.py +++ b/hikari/api/event_factory.py @@ -1429,7 +1429,7 @@ def deserialize_stage_instance_create_event( Returns ------- - hikari.events.voice_events.StageInstanceCreateEvent + hikari.events.stage_events.StageInstanceCreateEvent The parsed stage instance create event object. """ @@ -1448,7 +1448,7 @@ def deserialize_stage_instance_update_event( Returns ------- - hikari.events.voice_events.StageInstanceUpdateEvent + hikari.events.stage_events.StageInstanceUpdateEvent The parsed stage instance update event object. """ @@ -1467,6 +1467,6 @@ def deserialize_stage_instance_delete_event( Returns ------- - hikari.events.voice_events.StageInstanceDeleteEvent + hikari.events.stage_events.StageInstanceDeleteEvent The parsed stage instance delete event object. """ diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 2a0aa79ebe..e95632fe44 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8216,7 +8216,7 @@ async def fetch_stage_instance( Parameters ---------- - channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + channel The guild stage channel to fetch the stage instance from. Returns @@ -8260,18 +8260,13 @@ async def create_stage_instance( Parameters ---------- - channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + channel The channel to use for the stage instance creation. - - Other Parameters - ---------------- - topic: str + topic The topic for the stage instance. - - send_start_notification: hikari.undefined.UndefinedOr[bool] + send_start_notification Whether to send a notification to *all* server members that the stage instance has started. - - guild_scheduled_event_id: hikari.undefined.UndefinedOr[hikari.snowflakes.SnowflakeishOr[hikari.scheduled_events.ScheduledEvent]] + scheduled_event_id The ID of the scheduled event to associate with the stage instance. @@ -8312,12 +8307,9 @@ async def edit_stage_instance( Parameters ---------- - channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + channel The channel that the stage instance is associated with. - - Other Parameters - ---------------- - topic: hikari.undefined.UndefinedOr[str] + topic The topic for the stage instance. Returns @@ -8355,7 +8347,7 @@ async def delete_stage_instance( Parameters ---------- - channel: hikari.snowflakes.SnowflakeishOr[hikari.channels.GuildStageChannel] + channel The guild stage channel to fetch the stage instance from. Returns diff --git a/hikari/events/stage_events.py b/hikari/events/stage_events.py index 002c006cef..7942a3cf62 100644 --- a/hikari/events/stage_events.py +++ b/hikari/events/stage_events.py @@ -55,23 +55,11 @@ def app(self) -> traits.RESTAware: @property @abc.abstractmethod def stage_instance(self) -> StageInstance: - """Stage instance that this event relates to. - - Returns - ------- - hikari.stage_instance.StageInstance - The stage instance that this event relates to. - """ + """Stage instance that this event relates to.""" @property def stage_instance_id(self) -> snowflakes.Snowflake: - """ID of the stage instance that this event relates to. - - Returns - ------- - hikari.snowflakes.Snowflake - The ID of the stage instance that this event relates to. - """ + """ID of the stage instance that this event relates to.""" return self.stage_instance.id From 6dd22a4f9af3ad37070ba13d0d9e9d547329c6cc Mon Sep 17 00:00:00 2001 From: tandemdude <43570299+tandemdude@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:31:37 +0100 Subject: [PATCH 27/30] Address final comments, ensure pipelines pass --- hikari/api/rest.py | 4 +-- hikari/impl/entity_factory.py | 2 +- hikari/impl/rest.py | 4 +-- hikari/stage_instances.py | 7 +--- tests/hikari/impl/test_rest.py | 4 +-- tests/hikari/test_stage_instances.py | 53 ++-------------------------- 6 files changed, 9 insertions(+), 65 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index e95632fe44..46ba284211 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8252,7 +8252,7 @@ async def create_stage_instance( *, topic: str, send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, - guild_scheduled_event_id: undefined.UndefinedOr[ + scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] ] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: @@ -8294,7 +8294,7 @@ async def create_stage_instance( nature, and will trigger this exception if they occur. hikari.errors.InternalServerError If an internal error occurs on Discord while handling the request. - """ # noqa: E501 - Line too long + """ @abc.abstractmethod async def edit_stage_instance( diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index f41e415d6b..ae0846b38b 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1482,7 +1482,7 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ guild_id=snowflakes.Snowflake(payload["guild_id"]), topic=payload["topic"], discoverable_disabled=payload["discoverable_disabled"], - guild_scheduled_event_id=guild_scheduled_event_id, + scheduled_event_id=guild_scheduled_event_id, ) def deserialize_channel( diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index cb183eab97..4ce4398982 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -4483,7 +4483,7 @@ async def create_stage_instance( *, topic: str, send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, - guild_scheduled_event_id: undefined.UndefinedOr[ + scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] ] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: @@ -4492,7 +4492,7 @@ async def create_stage_instance( body.put_snowflake("channel_id", channel) body.put("topic", topic) body.put("send_start_notification", send_start_notification) - body.put_snowflake("guild_scheduled_event_id", guild_scheduled_event_id) + body.put_snowflake("guild_scheduled_event_id", scheduled_event_id) response = await self._request(route, json=body) assert isinstance(response, dict) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 5e38a0a716..0946db3192 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -29,15 +29,11 @@ import attr -from hikari import channels from hikari import scheduled_events from hikari import snowflakes from hikari import traits from hikari.internal import attrs_extensions -if typing.TYPE_CHECKING: - from hikari import guilds - @attr.define(hash=True, kw_only=True, weakref_slot=False) class StageInstance(snowflakes.Unique): @@ -63,8 +59,7 @@ class StageInstance(snowflakes.Unique): discoverable_disabled: bool = attr.field(eq=False, hash=False, repr=False) """Whether or not stage discovery is disabled.""" - guild_scheduled_event_id: typing.Optional[snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent]] = attr.field( + scheduled_event_id: typing.Optional[snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent]] = attr.field( eq=False, hash=False, repr=False ) """The ID of the scheduled event for this stage instance, if it exists.""" - diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py index 99a3c33df9..a10808adce 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -6463,9 +6463,7 @@ async def test_create_stage_instance(self, rest_client): } rest_client._request = mock.AsyncMock(return_value=mock_payload) - result = await rest_client.create_stage_instance( - channel=7334, topic="ur mom", guild_scheduled_event_id=3361203239 - ) + result = await rest_client.create_stage_instance(channel=7334, topic="ur mom", scheduled_event_id=3361203239) assert result is rest_client._entity_factory.deserialize_stage_instance.return_value rest_client._request.assert_called_once_with(expected_route, json=expected_json) diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 06ed7d5686..48a96d99c6 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -23,7 +23,6 @@ import mock import pytest -from hikari import channels from hikari import snowflakes from hikari import stage_instances @@ -43,7 +42,7 @@ def stage_instance(self, mock_app): guild_id=snowflakes.Snowflake(420), topic="beanos", discoverable_disabled=True, - guild_scheduled_event_id=snowflakes.Snowflake(1337), + scheduled_event_id=snowflakes.Snowflake(1337), ) def test_id_property(self, stage_instance): @@ -65,52 +64,4 @@ def test_discoverable_disabled_property(self, stage_instance): assert stage_instance.discoverable_disabled is True def test_guild_scheduled_event_id_property(self, stage_instance): - assert stage_instance.guild_scheduled_event_id == 1337 - - def test_get_channel(self, stage_instance): - mock_stage_channel = mock.Mock(channels.GuildStageChannel, channel_id=snowflakes.Snowflake(6969)) - stage_instance.app.cache.get_guild_channel = mock.Mock(return_value=mock_stage_channel) - - assert stage_instance.get_channel() == mock_stage_channel - stage_instance.app.cache.get_guild_channel.assert_called_once_with(6969) - - def test_get_channel_when_no_cache_trait(self, stage_instance): - stage_instance.app = object() - - assert stage_instance.get_channel() is None - - @pytest.mark.asyncio - async def test_fetch_channel(self, stage_instance): - mock_stage_channel = mock.Mock(channels.GuildStageChannel) - stage_instance.app.rest.fetch_channel = mock.AsyncMock(return_value=mock_stage_channel) - - assert await stage_instance.fetch_channel() == mock_stage_channel - stage_instance.app.rest.fetch_channel.assert_awaited_once_with(6969) - - def test_get_guild(self, stage_instance): - mock_guild = mock.Mock(channels.GuildStageChannel, guild_id=420) - stage_instance.app.cache.get_guild = mock.Mock(return_value=mock_guild) - - assert stage_instance.get_guild() == mock_guild - stage_instance.app.cache.get_guild.assert_called_once_with(420) - - def test_get_guild_when_no_cache_trait(self, stage_instance): - stage_instance.app = object() - - assert stage_instance.get_guild() is None - - @pytest.mark.asyncio - async def test_fetch_guild(self, stage_instance): - stage_instance.app.rest.fetch_guild = mock.AsyncMock() - - assert await stage_instance.fetch_guild() == stage_instance.app.rest.fetch_guild.return_value - stage_instance.app.rest.fetch_guild.assert_awaited_once_with(420) - - @pytest.mark.asyncio - async def test_fetch_scheduled_event(self, stage_instance): - stage_instance.app.rest.fetch_scheduled_event = mock.AsyncMock() - - f_s_e = await stage_instance.fetch_scheduled_event() - - assert f_s_e == stage_instance.app.rest.fetch_scheduled_event.return_value - stage_instance.app.rest.fetch_scheduled_event.assert_awaited_once_with(420, 1337) + assert stage_instance.scheduled_event_id == 1337 From 2cf82d7470a361afdcac55dd07c5aa919bf91af9 Mon Sep 17 00:00:00 2001 From: tandemdude <43570299+tandemdude@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:26:04 +0100 Subject: [PATCH 28/30] Implement StageInstancePrivacyLevel, fix incorrect tests --- hikari/api/rest.py | 17 +++++++------- hikari/impl/entity_factory.py | 1 + hikari/impl/rest.py | 16 +++++++++----- hikari/stage_instances.py | 16 +++++++++++++- tests/hikari/impl/test_entity_factory.py | 3 +++ tests/hikari/impl/test_rest.py | 28 +++++++++++------------- tests/hikari/test_stage_instances.py | 4 ++++ 7 files changed, 55 insertions(+), 30 deletions(-) diff --git a/hikari/api/rest.py b/hikari/api/rest.py index 46ba284211..7f9bda6e5e 100644 --- a/hikari/api/rest.py +++ b/hikari/api/rest.py @@ -8251,6 +8251,7 @@ async def create_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: str, + privacy_level: undefined.UndefinedOr[typing.Union[int, stage_instances.StageInstancePrivacyLevel]], send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] @@ -8264,6 +8265,8 @@ async def create_stage_instance( The channel to use for the stage instance creation. topic The topic for the stage instance. + privacy_level + The privacy level for the stage instance. send_start_notification Whether to send a notification to *all* server members that the stage instance has started. scheduled_event_id @@ -8302,6 +8305,9 @@ async def edit_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: undefined.UndefinedOr[str] = undefined.UNDEFINED, + privacy_level: undefined.UndefinedOr[ + typing.Union[int, stage_instances.StageInstancePrivacyLevel] + ] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: """Edit the stage instance in a guild stage channel. @@ -8311,6 +8317,8 @@ async def edit_stage_instance( The channel that the stage instance is associated with. topic The topic for the stage instance. + privacy_level: + The privacy level for the stage instance. Returns ------- @@ -8340,9 +8348,7 @@ async def edit_stage_instance( """ @abc.abstractmethod - async def delete_stage_instance( - self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] - ) -> stage_instances.StageInstance: + async def delete_stage_instance(self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel]) -> None: """Delete the stage instance. Parameters @@ -8350,11 +8356,6 @@ async def delete_stage_instance( channel The guild stage channel to fetch the stage instance from. - Returns - ------- - hikari.stage_instances.StageInstance - The stage instance that was deleted. - Raises ------ hikari.errors.UnauthorizedError diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py index ae0846b38b..d97f63136b 100644 --- a/hikari/impl/entity_factory.py +++ b/hikari/impl/entity_factory.py @@ -1481,6 +1481,7 @@ def deserialize_stage_instance(self, payload: data_binding.JSONObject) -> stage_ channel_id=snowflakes.Snowflake(payload["channel_id"]), guild_id=snowflakes.Snowflake(payload["guild_id"]), topic=payload["topic"], + privacy_level=stage_instances.StageInstancePrivacyLevel(payload["privacy_level"]), discoverable_disabled=payload["discoverable_disabled"], scheduled_event_id=guild_scheduled_event_id, ) diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py index 4ce4398982..e734cdb65b 100644 --- a/hikari/impl/rest.py +++ b/hikari/impl/rest.py @@ -4482,6 +4482,9 @@ async def create_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: str, + privacy_level: undefined.UndefinedOr[ + typing.Union[int, stage_instances.StageInstancePrivacyLevel] + ] = undefined.UNDEFINED, send_start_notification: undefined.UndefinedOr[bool] = undefined.UNDEFINED, scheduled_event_id: undefined.UndefinedOr[ snowflakes.SnowflakeishOr[scheduled_events.ScheduledEvent] @@ -4491,6 +4494,7 @@ async def create_stage_instance( body = data_binding.JSONObjectBuilder() body.put_snowflake("channel_id", channel) body.put("topic", topic) + body.put("privacy_level", privacy_level) body.put("send_start_notification", send_start_notification) body.put_snowflake("guild_scheduled_event_id", scheduled_event_id) @@ -4503,19 +4507,19 @@ async def edit_stage_instance( channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel], *, topic: undefined.UndefinedOr[str] = undefined.UNDEFINED, + privacy_level: undefined.UndefinedOr[ + typing.Union[int, stage_instances.StageInstancePrivacyLevel] + ] = undefined.UNDEFINED, ) -> stage_instances.StageInstance: route = routes.PATCH_STAGE_INSTANCE.compile(channel=channel) body = data_binding.JSONObjectBuilder() body.put("topic", topic) + body.put("privacy_level", privacy_level) response = await self._request(route, json=body) assert isinstance(response, dict) return self._entity_factory.deserialize_stage_instance(response) - async def delete_stage_instance( - self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel] - ) -> stage_instances.StageInstance: + async def delete_stage_instance(self, channel: snowflakes.SnowflakeishOr[channels_.GuildStageChannel]) -> None: route = routes.DELETE_STAGE_INSTANCE.compile(channel=channel) - response = await self._request(route) - assert isinstance(response, dict) - return self._entity_factory.deserialize_stage_instance(response) + await self._request(route) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index 0946db3192..ac0bff4e4a 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -23,7 +23,7 @@ """Application and entities that are used to describe stage instances on Discord.""" from __future__ import annotations -__all__: typing.Sequence[str] = ("StageInstance",) +__all__: typing.Sequence[str] = ("StageInstancePrivacyLevel", "StageInstance") import typing @@ -33,6 +33,17 @@ from hikari import snowflakes from hikari import traits from hikari.internal import attrs_extensions +from hikari.internal import enums + + +@typing.final +class StageInstancePrivacyLevel(int, enums.Enum): + """The privacy level of a stage instance.""" + + PUBLIC = 1 + """The Stage instance is visible publicly.""" + GUILD_ONLY = 2 + """The Stage instance is visible to only guild members.""" @attr.define(hash=True, kw_only=True, weakref_slot=False) @@ -56,6 +67,9 @@ class StageInstance(snowflakes.Unique): topic: str = attr.field(eq=False, hash=False, repr=False) """The topic of the stage instance.""" + privacy_level: StageInstancePrivacyLevel = attr.field(eq=False, hash=False, repr=False) + """The privacy level of the stage instance.""" + discoverable_disabled: bool = attr.field(eq=False, hash=False, repr=False) """Whether or not stage discovery is disabled.""" diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py index 83466438f1..fff056c67d 100644 --- a/tests/hikari/impl/test_entity_factory.py +++ b/tests/hikari/impl/test_entity_factory.py @@ -45,6 +45,7 @@ from hikari import scheduled_events as scheduled_event_models from hikari import sessions as gateway_models from hikari import snowflakes +from hikari import stage_instances as stage_instance_models from hikari import stickers as sticker_models from hikari import traits from hikari import undefined @@ -7222,6 +7223,7 @@ def stage_instance_payload(self): "guild_id": "197038439483310086", "channel_id": "733488538393510049", "topic": "Testing Testing, 123", + "privacy_level": 2, "guild_scheduled_event_id": "363820363920323120", "discoverable_disabled": False, } @@ -7234,4 +7236,5 @@ def test_deserialize_stage_instance(self, entity_factory_impl, stage_instance_pa assert stage_instance.channel_id == 733488538393510049 assert stage_instance.guild_id == 197038439483310086 assert stage_instance.topic == "Testing Testing, 123" + assert stage_instance.privacy_level == stage_instance_models.StageInstancePrivacyLevel.GUILD_ONLY assert stage_instance.discoverable_disabled is False diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py index a10808adce..74e90eadbf 100644 --- a/tests/hikari/impl/test_rest.py +++ b/tests/hikari/impl/test_rest.py @@ -47,6 +47,7 @@ from hikari import permissions from hikari import scheduled_events from hikari import snowflakes +from hikari import stage_instances from hikari import undefined from hikari import urls from hikari import users @@ -6444,7 +6445,7 @@ async def test_fetch_stage_instance(self, rest_client): } rest_client._request = mock.AsyncMock(return_value=mock_payload) - result = await rest_client.fetch_stage_instance(channel=123) + result = await rest_client.fetch_stage_instance(channel=StubModel(123)) assert result is rest_client._entity_factory.deserialize_stage_instance.return_value rest_client._request.assert_called_once_with(expected_route) @@ -6458,12 +6459,15 @@ async def test_create_stage_instance(self, rest_client): "guild_id": "19703", "channel_id": "7334", "topic": "ur mom", + "privacy_level": 2, "guild_scheduled_event_id": "3361203239", "discoverable_disabled": False, } rest_client._request = mock.AsyncMock(return_value=mock_payload) - result = await rest_client.create_stage_instance(channel=7334, topic="ur mom", scheduled_event_id=3361203239) + result = await rest_client.create_stage_instance( + channel=StubModel(7334), topic="ur mom", scheduled_event_id=StubModel(3361203239) + ) assert result is rest_client._entity_factory.deserialize_stage_instance.return_value rest_client._request.assert_called_once_with(expected_route, json=expected_json) @@ -6471,17 +6475,20 @@ async def test_create_stage_instance(self, rest_client): async def test_edit_stage_instance(self, rest_client): expected_route = routes.PATCH_STAGE_INSTANCE.compile(channel=7334) - expected_json = {"topic": "ur mom"} + expected_json = {"topic": "ur mom", "privacy_level": 2} mock_payload = { "id": "8406", "guild_id": "19703", "channel_id": "7334", "topic": "ur mom", + "privacy_level": 2, "discoverable_disabled": False, } rest_client._request = mock.AsyncMock(return_value=mock_payload) - result = await rest_client.edit_stage_instance(channel=7334, topic="ur mom") + result = await rest_client.edit_stage_instance( + channel=StubModel(7334), topic="ur mom", privacy_level=stage_instances.StageInstancePrivacyLevel.GUILD_ONLY + ) assert result is rest_client._entity_factory.deserialize_stage_instance.return_value rest_client._request.assert_called_once_with(expected_route, json=expected_json) @@ -6489,17 +6496,8 @@ async def test_edit_stage_instance(self, rest_client): async def test_delete_stage_instance(self, rest_client): expected_route = routes.DELETE_STAGE_INSTANCE.compile(channel=7334) - mock_payload = { - "id": "8406", - "guild_id": "19703", - "channel_id": "7334", - "topic": "ur mom", - "discoverable_disabled": False, - } - rest_client._request = mock.AsyncMock(return_value=mock_payload) + rest_client._request = mock.AsyncMock() - result = await rest_client.delete_stage_instance(channel=7334) + await rest_client.delete_stage_instance(channel=StubModel(7334)) - assert result is rest_client._entity_factory.deserialize_stage_instance.return_value rest_client._request.assert_called_once_with(expected_route) - rest_client._entity_factory.deserialize_stage_instance.assert_called_once_with(mock_payload) diff --git a/tests/hikari/test_stage_instances.py b/tests/hikari/test_stage_instances.py index 48a96d99c6..42e1289626 100644 --- a/tests/hikari/test_stage_instances.py +++ b/tests/hikari/test_stage_instances.py @@ -41,6 +41,7 @@ def stage_instance(self, mock_app): channel_id=snowflakes.Snowflake(6969), guild_id=snowflakes.Snowflake(420), topic="beanos", + privacy_level=stage_instances.StageInstancePrivacyLevel.GUILD_ONLY, discoverable_disabled=True, scheduled_event_id=snowflakes.Snowflake(1337), ) @@ -60,6 +61,9 @@ def test_guild_id_property(self, stage_instance): def test_topic_property(self, stage_instance): assert stage_instance.topic == "beanos" + def test_privacy_level_property(self, stage_instance): + assert stage_instance.privacy_level == stage_instances.StageInstancePrivacyLevel.GUILD_ONLY + def test_discoverable_disabled_property(self, stage_instance): assert stage_instance.discoverable_disabled is True From 944d49bb141c2fec9e7d567656d4bdca4e7389bb Mon Sep 17 00:00:00 2001 From: tandemdude <43570299+tandemdude@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:36:13 +0100 Subject: [PATCH 29/30] Remove stage_instance_id from events, fix formatting --- hikari/events/stage_events.py | 5 ----- hikari/stage_instances.py | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/hikari/events/stage_events.py b/hikari/events/stage_events.py index 7942a3cf62..bbb99cb26f 100644 --- a/hikari/events/stage_events.py +++ b/hikari/events/stage_events.py @@ -57,11 +57,6 @@ def app(self) -> traits.RESTAware: def stage_instance(self) -> StageInstance: """Stage instance that this event relates to.""" - @property - def stage_instance_id(self) -> snowflakes.Snowflake: - """ID of the stage instance that this event relates to.""" - return self.stage_instance.id - @attrs_extensions.with_copy @attr.define(kw_only=True, weakref_slot=False) diff --git a/hikari/stage_instances.py b/hikari/stage_instances.py index ac0bff4e4a..7a99781c4e 100644 --- a/hikari/stage_instances.py +++ b/hikari/stage_instances.py @@ -42,6 +42,7 @@ class StageInstancePrivacyLevel(int, enums.Enum): PUBLIC = 1 """The Stage instance is visible publicly.""" + GUILD_ONLY = 2 """The Stage instance is visible to only guild members.""" From aa0025b31d4117103885b47b825e7cae64ee2d36 Mon Sep 17 00:00:00 2001 From: tandemdude <43570299+tandemdude@users.noreply.github.com> Date: Sat, 3 Aug 2024 20:48:25 +0100 Subject: [PATCH 30/30] Fix tests and flake8 --- hikari/events/stage_events.py | 1 - tests/hikari/events/test_stage_events.py | 12 ------------ tests/hikari/impl/test_event_factory.py | 3 --- 3 files changed, 16 deletions(-) diff --git a/hikari/events/stage_events.py b/hikari/events/stage_events.py index bbb99cb26f..d5201d1e8d 100644 --- a/hikari/events/stage_events.py +++ b/hikari/events/stage_events.py @@ -36,7 +36,6 @@ from hikari.stage_instances import StageInstance if typing.TYPE_CHECKING: - from hikari import snowflakes from hikari import traits from hikari.api import shard as gateway_shard diff --git a/tests/hikari/events/test_stage_events.py b/tests/hikari/events/test_stage_events.py index 02aafe543c..38a8c6b9dc 100644 --- a/tests/hikari/events/test_stage_events.py +++ b/tests/hikari/events/test_stage_events.py @@ -35,10 +35,6 @@ def event(self): def test_app_property(self, event): assert event.app is event.stage_instance.app - def test_stage_instance_id_property(self, event): - event.stage_instance.id = 1234 - assert event.stage_instance_id == 1234 - class TestStageInstanceUpdateEvent: @pytest.fixture @@ -50,10 +46,6 @@ def event(self): def test_app_property(self, event): assert event.app is event.stage_instance.app - def test_stage_instance_id_property(self, event): - event.stage_instance.id = 1234 - assert event.stage_instance_id == 1234 - class TestStageInstanceDeleteEvent: @pytest.fixture @@ -64,7 +56,3 @@ def event(self): def test_app_property(self, event): assert event.app is event.stage_instance.app - - def test_stage_instance_id_property(self, event): - event.stage_instance.id = 1234 - assert event.stage_instance_id == 1234 diff --git a/tests/hikari/impl/test_event_factory.py b/tests/hikari/impl/test_event_factory.py index 1be7fb48f6..4fbf2c87fe 100644 --- a/tests/hikari/impl/test_event_factory.py +++ b/tests/hikari/impl/test_event_factory.py @@ -1537,7 +1537,6 @@ def test_deserialize_stage_instance_create_event(self, event_factory, mock_app, assert event.shard is mock_shard assert event.app is event.stage_instance.app - assert event.stage_instance_id == mock_app.entity_factory.deserialize_stage_instance.return_value.id assert event.stage_instance == mock_app.entity_factory.deserialize_stage_instance.return_value def test_deserialize_stage_instance_update_event(self, event_factory, mock_app, mock_shard): @@ -1554,7 +1553,6 @@ def test_deserialize_stage_instance_update_event(self, event_factory, mock_app, assert event.shard is mock_shard assert event.app is event.stage_instance.app - assert event.stage_instance_id == mock_app.entity_factory.deserialize_stage_instance.return_value.id assert event.stage_instance == mock_app.entity_factory.deserialize_stage_instance.return_value def test_deserialize_stage_instance_delete_event(self, event_factory, mock_app, mock_shard): @@ -1571,5 +1569,4 @@ def test_deserialize_stage_instance_delete_event(self, event_factory, mock_app, assert event.shard is mock_shard assert event.app is event.stage_instance.app - assert event.stage_instance_id == mock_app.entity_factory.deserialize_stage_instance.return_value.id assert event.stage_instance == mock_app.entity_factory.deserialize_stage_instance.return_value