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

feat: ✨ Add MediaChannel and fix ForumChannel flags #2641

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ These changes are available on the `master` branch, but have not yet been releas
`Permissions.use_external_sounds`, and
`Permissions.view_creator_monetization_analytics`.
([#2620](https://github.com/Pycord-Development/pycord/pull/2620))
- Added `MediaChannel` channel type.
([#2641](https://github.com/Pycord-Development/pycord/pull/2641))

### Fixed

Expand All @@ -60,6 +62,8 @@ These changes are available on the `master` branch, but have not yet been releas
([#2627](https://github.com/Pycord-Development/pycord/issues/2627))
- Fixed `AttributeError` when sending polls with `PartialWebook`.
([#2624](https://github.com/Pycord-Development/pycord/pull/2624))
- Fixed editing `ForumChannel` flags not working.
([#2641](https://github.com/Pycord-Development/pycord/pull/2641))
- Fixed `AttributeError` when accessing `Member.guild_permissions` for user installed
apps. ([#2650](https://github.com/Pycord-Development/pycord/pull/2650))
- Fixed type annotations of cached properties.
Expand Down
11 changes: 4 additions & 7 deletions discord/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from __future__ import annotations

import asyncio
import contextlib
import copy
import time
from typing import (
Expand All @@ -46,7 +47,7 @@
from .enums import ChannelType
from .errors import ClientException, InvalidArgument
from .file import File
from .flags import MessageFlags
from .flags import ChannelFlags, MessageFlags
from .invite import Invite
from .iterators import HistoryIterator
from .mentions import AllowedMentions
Expand Down Expand Up @@ -85,7 +86,6 @@
from .client import Client
from .embeds import Embed
from .enums import InviteTarget
from .flags import ChannelFlags
from .guild import Guild
from .member import Member
from .message import Message, MessageReference, PartialMessage
Expand Down Expand Up @@ -418,11 +418,8 @@ async def _edit(
except KeyError:
pass

try:
if options.pop("require_tag"):
options["flags"] = ChannelFlags.require_tag.flag
except KeyError:
pass
with contextlib.suppress(KeyError):
options["flags"] = options.pop("flags").value

try:
options["available_tags"] = [
Expand Down
205 changes: 203 additions & 2 deletions discord/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from __future__ import annotations

import contextlib
import datetime
from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, TypeVar, overload

Expand Down Expand Up @@ -65,6 +66,7 @@
"GroupChannel",
"PartialMessageable",
"ForumChannel",
"MediaChannel",
"ForumTag",
)

Expand Down Expand Up @@ -853,7 +855,6 @@ async def edit(self, *, reason=None, **options):
HTTPException
Editing the channel failed.
"""

payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
Expand Down Expand Up @@ -1056,7 +1057,7 @@ def guidelines(self) -> str | None:

@property
def requires_tag(self) -> bool:
"""Whether a tag is required to be specified when creating a thread in this forum channel.
"""Whether a tag is required to be specified when creating a thread in this forum or media channel.

Tags are specified in :attr:`applied_tags`.

Expand Down Expand Up @@ -1169,6 +1170,10 @@ async def edit(self, *, reason=None, **options):
HTTPException
Editing the channel failed.
"""
with contextlib.suppress(KeyError):
require_tag = options.pop("require_tag")
options["flags"] = ChannelFlags._from_value(self.flags.value)
options["flags"].require_tag = require_tag

payload = await self._edit(options, reason=reason)
if payload is not None:
Expand Down Expand Up @@ -1351,6 +1356,198 @@ async def create_thread(
return ret


class MediaChannel(ForumChannel):
"""Represents a Discord media channel. Subclass of :class:`ForumChannel`.

.. container:: operations

.. describe:: x == y

Checks if two channels are equal.

.. describe:: x != y

Checks if two channels are not equal.

.. describe:: hash(x)

Returns the channel's hash.

.. describe:: str(x)

Returns the channel's name.

Paillat-dev marked this conversation as resolved.
Show resolved Hide resolved
.. versionadded:: 2.7

Attributes
----------
name: :class:`str`
The channel name.
guild: :class:`Guild`
The guild the channel belongs to.
id: :class:`int`
The channel ID.
category_id: Optional[:class:`int`]
The category channel ID this channel belongs to, if applicable.
topic: Optional[:class:`str`]
The channel's topic. ``None`` if it doesn't exist.

.. note::

:attr:`guidelines` exists as an alternative to this attribute.
position: Optional[:class:`int`]
The position in the channel list. This is a number that starts at 0. e.g. the
top channel is position 0. Can be ``None`` if the channel was received in an interaction.
last_message_id: Optional[:class:`int`]
The last message ID of the message sent to this channel. It may
*not* point to an existing or valid message.
slowmode_delay: :class:`int`
The number of seconds a member must wait between sending messages
in this channel. A value of `0` denotes that it is disabled.
Bots and users with :attr:`~Permissions.manage_channels` or
:attr:`~Permissions.manage_messages` bypass slowmode.
nsfw: :class:`bool`
If the channel is marked as "not safe for work".

.. note::

To check if the channel or the guild of that channel are marked as NSFW, consider :meth:`is_nsfw` instead.
default_auto_archive_duration: :class:`int`
The default auto archive duration in minutes for threads created in this channel.

flags: :class:`ChannelFlags`
Extra features of the channel.

available_tags: List[:class:`ForumTag`]
The set of tags that can be used in a forum channel.

default_sort_order: Optional[:class:`SortOrder`]
The default sort order type used to order posts in this channel.

default_thread_slowmode_delay: Optional[:class:`int`]
The initial slowmode delay to set on newly created threads in this channel.

default_reaction_emoji: Optional[:class:`str` | :class:`discord.GuildEmoji`]
The default forum reaction emoji.
"""

@property
def hides_media_download_options(self) -> bool:
"""Whether media download options are hidden in this media channel."""
return self.flags.hide_media_download_options

@overload
async def edit(
self,
*,
reason: str | None = ...,
name: str = ...,
topic: str = ...,
position: int = ...,
nsfw: bool = ...,
sync_permissions: bool = ...,
category: CategoryChannel | None = ...,
slowmode_delay: int = ...,
default_auto_archive_duration: ThreadArchiveDuration = ...,
default_thread_slowmode_delay: int = ...,
default_sort_order: SortOrder = ...,
default_reaction_emoji: GuildEmoji | int | str | None = ...,
available_tags: list[ForumTag] = ...,
require_tag: bool = ...,
hide_media_download_options: bool = ...,
overwrites: Mapping[Role | Member | Snowflake, PermissionOverwrite] = ...,
) -> ForumChannel | None: ...

async def edit(self, *, reason=None, **options):
"""|coro|

Edits the channel.

You must have the :attr:`~Permissions.manage_channels` permission to
use this.

Parameters
----------
name: :class:`str`
The new channel name.
topic: :class:`str`
The new channel's topic.
position: :class:`int`
The new channel's position.
nsfw: :class:`bool`
To mark the channel as NSFW or not.
sync_permissions: :class:`bool`
Whether to sync permissions with the channel's new or pre-existing
category. Defaults to ``False``.
category: Optional[:class:`CategoryChannel`]
The new category for this channel. Can be ``None`` to remove the
category.
slowmode_delay: :class:`int`
Specifies the slowmode rate limit for user in this channel, in seconds.
A value of `0` disables slowmode. The maximum value possible is `21600`.
reason: Optional[:class:`str`]
The reason for editing this channel. Shows up on the audit log.
overwrites: Dict[Union[:class:`Role`, :class:`Member`, :class:`~discord.abc.Snowflake`], :class:`PermissionOverwrite`]
The overwrites to apply to channel permissions. Useful for creating secret channels.
default_auto_archive_duration: :class:`int`
The new default auto archive duration in minutes for threads created in this channel.
Must be one of ``60``, ``1440``, ``4320``, or ``10080``.
default_thread_slowmode_delay: :class:`int`
The new default slowmode delay in seconds for threads created in this channel.

default_sort_order: Optional[:class:`SortOrder`]
The default sort order type to use to order posts in this channel.

default_reaction_emoji: Optional[:class:`discord.GuildEmoji` | :class:`int` | :class:`str`]
The default reaction emoji.
Can be a unicode emoji or a custom emoji in the forms:
:class:`GuildEmoji`, snowflake ID, string representation (e.g., '<a:emoji_name:emoji_id>').

available_tags: List[:class:`ForumTag`]
The set of tags that can be used in this channel. Must be less than `20`.

require_tag: :class:`bool`
Whether a tag should be required to be specified when creating a thread in this channel.

hide_media_download_options: :class:`bool`
Whether media download options should be hidden in this media channel.

Returns
-------
Optional[:class:`.MediaChannel`]
The newly edited media channel. If the edit was only positional
then ``None`` is returned instead.

Raises
------
InvalidArgument
If position is less than 0 or greater than the number of channels, or if
the permission overwrite information is not in proper form.
Forbidden
You do not have permissions to edit the channel.
HTTPException
Editing the channel failed.
"""
with contextlib.suppress(KeyError):
require_tag = options.pop("require_tag")
options["flags"] = options.get("flags") or ChannelFlags._from_value(
self.flags.value
)
options["flags"].require_tag = require_tag

with contextlib.suppress(KeyError):
hide_media_download_options = options.pop("hide_media_download_options")
options["flags"] = options.get("flags") or ChannelFlags._from_value(
self.flags.value
)
options["flags"].hide_media_download_options = hide_media_download_options

payload = await self._edit(options, reason=reason)
if payload is not None:
# the payload will always be the proper channel payload
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore


class VocalGuildChannel(discord.abc.Connectable, discord.abc.GuildChannel, Hashable):
__slots__ = (
"name",
Expand Down Expand Up @@ -3232,8 +3429,12 @@ def _guild_channel_factory(channel_type: int):
return TextChannel, value
elif value is ChannelType.stage_voice:
return StageChannel, value
elif value is ChannelType.directory:
return None, value # todo: Add DirectoryChannel when applicable
elif value is ChannelType.forum:
return ForumChannel, value
elif value is ChannelType.media:
return MediaChannel, value
else:
return None, value

Expand Down
2 changes: 2 additions & 0 deletions discord/commands/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
CategoryChannel,
DMChannel,
ForumChannel,
MediaChannel,
StageChannel,
TextChannel,
Thread,
Expand Down Expand Up @@ -85,6 +86,7 @@
CategoryChannel: ChannelType.category,
Thread: ChannelType.public_thread,
ForumChannel: ChannelType.forum,
MediaChannel: ChannelType.media,
DMChannel: ChannelType.private,
}

Expand Down
1 change: 1 addition & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ def from_datatype(cls, datatype):
"ThreadOption",
"Thread",
"ForumChannel",
"MediaChannel",
"DMChannel",
]:
return cls.channel
Expand Down
8 changes: 8 additions & 0 deletions discord/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,14 @@ def require_tag(self):
"""
return 1 << 4

@flag_value
def hide_media_download_options(self):
""":class:`bool`: Returns ``True`` if the embedded media download options are hidden for the media channel posts.

.. versionadded:: 2.7
"""
return 1 << 15


@fill_with_flags()
class AttachmentFlags(BaseFlags):
Expand Down
6 changes: 3 additions & 3 deletions discord/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def members(self) -> list[ThreadMember]:
def applied_tags(self) -> list[ForumTag]:
"""List[:class:`ForumTag`]: A list of tags applied to this thread.

This is only available for threads in forum channels.
This is only available for threads in forum or media channels.
"""
from .channel import ForumChannel # to prevent circular import

Expand Down Expand Up @@ -394,7 +394,7 @@ def starting_message(self) -> Message | None:
return self._state._get_message(self.id)

def is_pinned(self) -> bool:
"""Whether the thread is pinned to the top of its parent forum channel.
"""Whether the thread is pinned to the top of its parent forum or media channel.

.. versionadded:: 2.3
"""
Expand Down Expand Up @@ -638,7 +638,7 @@ async def edit(
reason: Optional[:class:`str`]
The reason for editing this thread. Shows up on the audit log.
pinned: :class:`bool`
Whether to pin the thread or not. This only works if the thread is part of a forum.
Whether to pin the thread or not. This only works if the thread is part of a forum or media channel.
applied_tags: List[:class:`ForumTag`]
The set of tags to apply to the thread. Each tag object should have an ID set.

Expand Down
6 changes: 6 additions & 0 deletions docs/api/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,12 @@ Channels
:members:
:inherited-members:

.. attributetable:: MediaChannel

.. autoclass:: MediaChannel()
:members:
:inherited-members:

.. attributetable:: VoiceChannel

.. autoclass:: VoiceChannel()
Expand Down
Loading