Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add media channel support #1687

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/idk.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for guild media channels.
39 changes: 39 additions & 0 deletions hikari/api/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,45 @@ def deserialize_guild_private_thread(
`"guild_id"` is not present in the passed payload.
"""

@abc.abstractmethod
def deserialize_guild_media_channel(
self,
payload: data_binding.JSONObject,
*,
guild_id: undefined.UndefinedOr[snowflakes.Snowflake] = undefined.UNDEFINED,
) -> channel_models.GuildMediaChannel:
"""Parse a raw payload from Discord into a guild media channel object.

Parameters
----------
payload : hikari.internal.data_binding.JSONObject
The JSON payload to deserialize.

Other Parameters
----------------
guild_id : hikari.undefined.UndefinedOr[hikari.snowflakes.Snowflake]
The ID of the guild this channel belongs to. This will be ignored
for DM and group DM channels and will be prioritised over
`"guild_id"` in the payload when passed.

This is necessary in GUILD_CREATE events, where `"guild_id"` is not
included in the channel's payload

Returns
-------
hikari.channels.GuildMediaChannel
The deserialized guild media channel object.

Raises
------
KeyError
If `guild_id` is left as `hikari.undefined.UNDEFINED` when
`"guild_id"` is not present in the passed payload of a guild
channel.
hikari.errors.UnrecognisedEntityError
If the channel type is unknown.
"""

@abc.abstractmethod
def deserialize_channel(
self,
Expand Down
75 changes: 75 additions & 0 deletions hikari/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"ForumLayoutType",
"ForumTag",
"GuildForumChannel",
"GuildMediaChannel",
"GuildVoiceChannel",
"GuildStageChannel",
"WebhookChannelT",
Expand Down Expand Up @@ -129,6 +130,10 @@ class ChannelType(int, enums.Enum):
GUILD_FORUM = 15
"""A channel consisting of a collection of public guild threads."""

GUILD_MEDIA = 16
""""""



@typing.final
class ChannelFlag(enums.Flag):
Expand Down Expand Up @@ -1707,3 +1712,73 @@ class GuildPrivateThread(GuildThreadChannel):

is_invitable: bool = attrs.field(eq=False, hash=False, repr=True)
"""Whether non-moderators can add other non-moderators to a private thread."""


@attrs.define(hash=True, kw_only=True, weakref_slot=False)
class GuildMediaChannel(PermissibleGuildChannel):
"""Represents a guild media channel."""

topic: typing.Optional[str] = attrs.field(eq=False, hash=False, repr=False)
"""The guidelines for the channel."""

last_thread_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=False)
"""The ID of the last thread created in this channel.

.. warning::
This might point to an invalid or deleted message. Do not assume that
this will always be valid.
"""

rate_limit_per_user: datetime.timedelta = attrs.field(eq=False, hash=False, repr=False)
"""The delay (in seconds) between a user can create threads in this channel.

If there is no rate limit, this will be 0 seconds.

.. note::
Any user that has permissions allowing `MANAGE_MESSAGES`,
`MANAGE_CHANNEL`, `ADMINISTRATOR` will not be limited. Likewise, bots
will not be affected by this rate limit.
"""

default_thread_rate_limit_per_user: datetime.timedelta = attrs.field(eq=False, hash=False, repr=False)
"""The default delay (in seconds) between a user can send a message in created threads.

If there is no rate limit, this will be 0 seconds.

.. note::
Any user that has permissions allowing `MANAGE_MESSAGES`,
`MANAGE_CHANNEL`, `ADMINISTRATOR` will not be limited. Likewise, bots
will not be affected by this rate limit.
"""

default_auto_archive_duration: datetime.timedelta = attrs.field(eq=False, hash=False, repr=False)
"""The auto archive duration Discord's client defaults to for threads in this channel.

This may be be either 1 hour, 1 day, 3 days or 1 week.
"""

flags: ChannelFlag = attrs.field(eq=False, hash=False, repr=False)
"""The channel flags for this channel.

.. note::
As of writing, the only flag that can be set is `ChannelFlag.REQUIRE_TAG`.
"""

available_tags: typing.Sequence[ForumTag] = attrs.field(eq=False, hash=False, repr=False)
"""The available tags to select from when creating a thread."""

default_sort_order: ForumSortOrderType = attrs.field(eq=False, hash=False, repr=False)
"""The default sort order for the forum."""

default_reaction_emoji_id: typing.Optional[snowflakes.Snowflake] = attrs.field(eq=False, hash=False, repr=False)
"""The ID of the default reaction emoji."""

default_reaction_emoji_name: typing.Union[str, emojis.UnicodeEmoji, None] = attrs.field(
eq=False, hash=False, repr=False
)
"""Name of the default reaction emoji.

Either the string name of the custom emoji, the object
of the `hikari.emojis.UnicodeEmoji` or `None` when the relevant
custom emoji's data is not available (e.g. the emoji has been deleted).
"""
82 changes: 78 additions & 4 deletions hikari/impl/entity_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ def __init__(self, app: traits.RESTAware) -> None:
channel_models.ChannelType.GUILD_VOICE: self.deserialize_guild_voice_channel,
channel_models.ChannelType.GUILD_STAGE: self.deserialize_guild_stage_channel,
channel_models.ChannelType.GUILD_FORUM: self.deserialize_guild_forum_channel,
channel_models.ChannelType.GUILD_MEDIA: self.deserialize_guild_media_channel
}
self._thread_channel_type_mapping = {
channel_models.ChannelType.GUILD_NEWS_THREAD: self.deserialize_guild_news_thread,
Expand Down Expand Up @@ -1309,7 +1310,7 @@ def deserialize_guild_thread(
if deserialize := self._thread_channel_type_mapping.get(channel_type):
return deserialize(payload, guild_id=guild_id, member=member, user_id=user_id)

_LOGGER.debug(f"Unrecognised thread channel type {channel_type}")
_LOGGER.debug("Unrecognised thread channel type %s", channel_type)
raise errors.UnrecognisedEntityError(f"Unrecognised thread channel type {channel_type}")

def deserialize_guild_news_thread(
Expand Down Expand Up @@ -1464,6 +1465,79 @@ def deserialize_guild_private_thread(
is_invitable=metadata["invitable"],
thread_created_at=thread_created_at,
)

def deserialize_guild_media_channel(
self,
payload: data_binding.JSONObject,
*,
guild_id: undefined.UndefinedOr[snowflakes.Snowflake] = undefined.UNDEFINED,
) -> channel_models.GuildMediaChannel:
channel_fields = self._set_guild_channel_attrsibutes(payload, guild_id=guild_id)

# Discord's docs are just wrong about this always being included.
default_auto_archive_duration = datetime.timedelta(minutes=payload.get("default_auto_archive_duration", 1440))
default_thread_rate_limit_per_user = datetime.timedelta(
seconds=payload.get("default_thread_rate_limit_per_user", 0)
)

permission_overwrites = {
snowflakes.Snowflake(overwrite["id"]): self.deserialize_permission_overwrite(overwrite)
for overwrite in payload["permission_overwrites"]
}

last_thread_id: typing.Optional[snowflakes.Snowflake] = None
if raw_last_thread_id := payload.get("last_message_id"):
last_thread_id = snowflakes.Snowflake(raw_last_thread_id)

available_tags: typing.List[channel_models.ForumTag] = []
for tag_payload in payload.get("available_tags", ()):
tag_emoji: typing.Union[emoji_models.UnicodeEmoji, snowflakes.Snowflake, None]
if tag_emoji := tag_payload["emoji_id"]:
tag_emoji = snowflakes.Snowflake(tag_emoji)

elif tag_emoji := tag_payload["emoji_name"]:
tag_emoji = emoji_models.UnicodeEmoji(tag_emoji)

available_tags.append(
channel_models.ForumTag(
id=snowflakes.Snowflake(tag_payload["id"]),
name=tag_payload["name"],
moderated=tag_payload["moderated"],
emoji=tag_emoji,
)
)

reaction_emoji_id: typing.Optional[snowflakes.Snowflake] = None
reaction_emoji_name: typing.Union[None, emoji_models.UnicodeEmoji, str] = None
if reaction_emoji_payload := payload.get("default_reaction_emoji"):
if reaction_emoji_id := reaction_emoji_payload["emoji_id"]:
reaction_emoji_id = snowflakes.Snowflake(reaction_emoji_id)

if reaction_emoji_name := reaction_emoji_payload["emoji_name"]:
reaction_emoji_name = emoji_models.UnicodeEmoji(reaction_emoji_name)

return channel_models.GuildMediaChannel(
app=self._app,
id=channel_fields.id,
name=channel_fields.name,
type=channel_fields.type,
guild_id=channel_fields.guild_id,
permission_overwrites=permission_overwrites,
is_nsfw=payload.get("nsfw", False),
parent_id=channel_fields.parent_id,
topic=payload["topic"],
last_thread_id=last_thread_id,
rate_limit_per_user=datetime.timedelta(seconds=payload.get("rate_limit_per_user", 0)),
default_thread_rate_limit_per_user=default_thread_rate_limit_per_user,
default_auto_archive_duration=default_auto_archive_duration,
position=int(payload["position"]),
available_tags=available_tags,
flags=channel_models.ChannelFlag(payload["flags"]),
# Discord's docs are just wrong about this never being null.
default_sort_order=channel_models.ForumSortOrderType(payload.get("default_sort_order") or 0),
default_reaction_emoji_id=reaction_emoji_id,
default_reaction_emoji_name=reaction_emoji_name,
)

def deserialize_channel(
self,
Expand All @@ -1481,7 +1555,7 @@ def deserialize_channel(
if dm_channel_model := self._dm_channel_type_mapping.get(channel_type):
return dm_channel_model(payload)

_LOGGER.debug(f"Unrecognised channel type {channel_type}")
_LOGGER.debug("Unrecognised channel type %s", channel_type)
raise errors.UnrecognisedEntityError(f"Unrecognised channel type {channel_type}")

################
Expand Down Expand Up @@ -3427,7 +3501,7 @@ def deserialize_scheduled_event(self, payload: data_binding.JSONObject) -> sched
if converter := self._scheduled_event_type_mapping.get(event_type):
return converter(payload)

_LOGGER.debug(f"Unrecognised scheduled event type {event_type}")
_LOGGER.debug("Unrecognised scheduled event type %s", event_type)
raise errors.UnrecognisedEntityError(f"Unrecognised scheduled event type {event_type}")

def deserialize_scheduled_event_user(
Expand Down Expand Up @@ -3701,5 +3775,5 @@ def deserialize_webhook(self, payload: data_binding.JSONObject) -> webhook_model
if converter := self._webhook_type_mapping.get(webhook_type):
return converter(payload)

_LOGGER.debug(f"Unrecognised webhook type {webhook_type}")
_LOGGER.debug("Unrecognised webhook type %s", webhook_type)
raise errors.UnrecognisedEntityError(f"Unrecognised webhook type {webhook_type}")
Loading