Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3c61693
feat: deprecate VoiceRegion enum in favour of dynamic voice regions
vmphase Aug 11, 2026
ddd0d1b
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2026
b9e9c6c
feat(enums): deprecate VoiceRegion enum for LSP; warn on VoiceRegion("")
vmphase Aug 11, 2026
19dfe2a
feat(enums): unknown attribute fallback
vmphase Aug 11, 2026
53eb128
feat: make rtc_region paramater accept str (region id) for channel
vmphase Aug 11, 2026
12acd61
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2026
2e474dc
refactor(enums): import ordering
vmphase Aug 11, 2026
0a47380
Update discord/guild.py
vmphase Aug 11, 2026
9faa308
refactor(types): make VoiceRegion a dataclass
vmphase Aug 11, 2026
0927a51
refactor(regions): apply review suggestions
vmphase Aug 11, 2026
fe66309
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2026
fd2b819
chore: rename VoiceRegion to VoiceRegionType for consistency with
vmphase Aug 11, 2026
74d6623
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2026
defeffb
refactor: change dataclass naming
vmphase Aug 11, 2026
82bf07f
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2026
3278b89
refactor: apply code review suggestions
vmphase Aug 12, 2026
4cc7b32
Merge branch 'master' into feat/dynamic-voice-regions
vmphase Aug 31, 2026
db041ba
chore: address wording review
vmphase Sep 11, 2026
bbdbba5
Merge branch 'master' into feat/dynamic-voice-regions
vmphase Sep 12, 2026
62f1497
Merge branch 'master' into feat/dynamic-voice-regions
vmphase Sep 18, 2026
5f38f22
fix(types/voice): remove obsolete "vip" field from VoiceRegionPayload
vmphase Sep 29, 2026
3effff4
chore: apply batched suggesstions
vmphase Sep 29, 2026
89c7fa4
fix(guild): export VoiceServerRegion
vmphase Sep 29, 2026
03a80a4
refactor(guild): explicitly specify payload fields
vmphase Sep 29, 2026
eff65e1
feat(enums): normalize _ to -
vmphase Sep 30, 2026
f194316
docs(guild): add VoiceServerRegion to the API documentation
vmphase Oct 4, 2026
79c69a3
Merge branch 'master' into feat/dynamic-voice-regions
vmphase Oct 4, 2026
1fb0340
Merge branch 'master' into feat/dynamic-voice-regions
vmphase Oct 5, 2026
6fa48cb
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 5, 2026
1009e74
Merge branch 'master' into feat/dynamic-voice-regions
vmphase Oct 6, 2026
4c742de
chore: update changelog
vmphase Oct 8, 2026
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ These changes are available on the `master` branch, but have not yet been releas
([#3328](https://github.com/Pycord-Development/pycord/pull/3328))
- Added `SlashCommandGroup.add_command`.
([#3346](https://github.com/Pycord-Development/pycord/pull/3346))
- Added `Guild.fetch_voice_regions()` method to retrieve the currently available voice
regions for the guild.
([#3347](https://github.com/Pycord-Development/pycord/pull/3347))

### Changed

- Simplify and optimize the `utils.get()` function.
([#3283](https://github.com/Pycord-Development/pycord/pull/3283))
- Changed `rtc_region` parameters of channel creation and edit methods to accept a
region ID `str`. ([#3347](https://github.com/Pycord-Development/pycord/pull/3347))

### Fixed

Expand All @@ -37,6 +42,8 @@ These changes are available on the `master` branch, but have not yet been releas

- Deprecated `Invite.id` in favour of `Invite.code`.
([#3313](https://github.com/Pycord-Development/pycord/pull/3313))
- Deprecated the `VoiceRegion` enum in favor of `Guild.fetch_voice_regions()`.
([#3347](https://github.com/Pycord-Development/pycord/pull/3347))

### Removed

Expand Down
20 changes: 14 additions & 6 deletions discord/channel.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2091,7 +2091,7 @@ async def edit(
sync_permissions: int = ...,
category: CategoryChannel | None = ...,
overwrites: Mapping[Role | Member, PermissionOverwrite] = ...,
rtc_region: VoiceRegion | None = ...,
rtc_region: VoiceRegion | str | None = ...,
video_quality_mode: VideoQualityMode = ...,
slowmode_delay: int = ...,
nsfw: bool = ...,
Expand Down Expand Up @@ -2135,10 +2135,14 @@ async def edit(self, *, reason=None, **options):
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.
rtc_region: Optional[:class:`VoiceRegion`]
The new region for the voice channel's voice communication.
rtc_region: Optional[Union[:class:`str`, :class:`VoiceRegion`]]
Comment thread
vmphase marked this conversation as resolved.
The new region ID for the voice channel's voice communication.
A value of ``None`` indicates automatic voice region detection.

.. versionchanged:: 2.9

A region ID string is now accepted.

.. versionadded:: 1.7
video_quality_mode: :class:`VideoQualityMode`
The camera video quality for the voice channel's participants.
Expand Down Expand Up @@ -2778,7 +2782,7 @@ async def edit(
sync_permissions: int = ...,
category: CategoryChannel | None = ...,
overwrites: Mapping[Role | Member, PermissionOverwrite] = ...,
rtc_region: VoiceRegion | None = ...,
rtc_region: VoiceRegion | str | None = ...,
video_quality_mode: VideoQualityMode = ...,
reason: str | None = ...,
) -> StageChannel | None: ...
Expand Down Expand Up @@ -2816,9 +2820,13 @@ async def edit(self, *, reason=None, **options):
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.
rtc_region: Optional[:class:`VoiceRegion`]
The new region for the stage channel's voice communication.
rtc_region: Optional[Union[:class:`str`, :class:`VoiceRegion`]]
Comment thread
vmphase marked this conversation as resolved.
The new region ID for the stage channel's voice communication.
A value of ``None`` indicates automatic voice region detection.

.. versionchanged:: 2.9

A :class:`VoiceRegion` member is now accepted.
video_quality_mode: :class:`VideoQualityMode`
The camera video quality for the stage channel's participants.

Expand Down
108 changes: 75 additions & 33 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,23 @@

import types
from collections import namedtuple
from collections.abc import Callable
from enum import IntEnum
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, Union

from .utils import warn_deprecated

if TYPE_CHECKING:
from typing_extensions import deprecated
else:

def deprecated(message: str) -> Callable[[T], T]:
def decorator(value: T) -> T:
return value

return decorator


__all__ = (
"Enum",
"ChannelType",
Expand Down Expand Up @@ -96,21 +110,17 @@ def _create_value_cls(name, comparable):
cls.__repr__ = lambda self: f"<{name}.{self.name}: {self.value!r}>"
cls.__str__ = lambda self: f"{name}.{self.name}"
if comparable:
cls.__le__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value <= other.value
cls.__le__ = lambda self, other: (
isinstance(other, self.__class__) and self.value <= other.value
)
cls.__ge__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value >= other.value
cls.__ge__ = lambda self, other: (
isinstance(other, self.__class__) and self.value >= other.value
)
cls.__lt__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value < other.value
cls.__lt__ = lambda self, other: (
isinstance(other, self.__class__) and self.value < other.value
)
cls.__gt__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value > other.value
cls.__gt__ = lambda self, other: (
isinstance(other, self.__class__) and self.value > other.value
)
return cls

Expand Down Expand Up @@ -286,32 +296,64 @@ class MessageType(Enum):
poll_result = 46


class VoiceRegion(Enum):
"""Voice region"""
class _VoiceRegionMeta(Enum.__class__):
def _warn(self, label: str) -> None:
warn_deprecated(
label,
instead="the region ID string or Guild.fetch_voice_regions()",
since="2.9",
removed="3.0",
stacklevel=4,
)

def __getattribute__(cls, name: str) -> Any:
members = super().__getattribute__("_enum_member_map_")
if name in members:
cls._warn(f"VoiceRegion.{name}")
return super().__getattribute__(name)

def __getitem__(cls, name: str) -> Any:
member = super().__getitem__(name)
cls._warn(f"VoiceRegion[{name!r}]")
return member

def __call__(cls, *args: Any, **kwargs: Any) -> Any:
cls._warn(f"VoiceRegion({', '.join(map(repr, args))})")
return super().__call__(*args, **kwargs)

def __getattr__(cls, name: str) -> Any:
if name.startswith("_"):
raise AttributeError(name)
cls._warn(f"VoiceRegion.{name}")
return create_unknown_value(cls, name.replace("_", "-"))


@deprecated(
"VoiceRegion is deprecated since version 2.9 and will be removed in version 3.0. "
"Use Guild.fetch_voice_regions() to retrieve available regions as a list of VoiceServerRegion objects."
)
class VoiceRegion(Enum, metaclass=_VoiceRegionMeta):
Comment thread
vmphase marked this conversation as resolved.
"""Specifies the region a voice server belongs to.

.. deprecated:: 2.9
The list of voice regions is dynamic, so this enum is deprecated.
Use :meth:`Guild.fetch_voice_regions` to retrieve available regions
as a list of :class:`VoiceServerRegion` objects. This enum will be removed in version 3.0.
"""

us_west = "us-west"
us_east = "us-east"
us_south = "us-south"
us_central = "us-central"
eu_west = "eu-west"
eu_central = "eu-central"
singapore = "singapore"
london = "london"
sydney = "sydney"
amsterdam = "amsterdam"
frankfurt = "frankfurt"
brazil = "brazil"
hongkong = "hongkong"
russia = "russia"
india = "india"
japan = "japan"
southafrica = "southafrica"
rotterdam = "rotterdam"
singapore = "singapore"
south_korea = "south-korea"
india = "india"
europe = "europe"
dubai = "dubai"
vip_us_east = "vip-us-east"
vip_us_west = "vip-us-west"
vip_amsterdam = "vip-amsterdam"
southafrica = "southafrica"
sydney = "sydney"
us_central = "us-central"
us_east = "us-east"
us_south = "us-south"
us_west = "us-west"

Comment thread
vmphase marked this conversation as resolved.
def __str__(self):
return self.value
Expand Down
85 changes: 78 additions & 7 deletions discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import datetime
import unicodedata
from collections.abc import Sequence
from dataclasses import dataclass
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -97,7 +98,7 @@
from .welcome_screen import WelcomeScreen, WelcomeScreenChannel
from .widget import Widget

__all__ = ("BanEntry", "Guild", "GuildRoleCounts")
__all__ = ("BanEntry", "Guild", "GuildRoleCounts", "VoiceServerRegion")

MISSING = utils.MISSING

Expand Down Expand Up @@ -149,6 +150,36 @@ class _GuildLimit(NamedTuple):
filesize: int


@dataclass(frozen=True, slots=True)
class VoiceServerRegion:
"""Represents a voice region a guild can use for voice channels.

This is returned by :meth:`Guild.fetch_voice_regions`.
Comment thread
vmphase marked this conversation as resolved.
Comment thread
vmphase marked this conversation as resolved.

.. versionadded:: 2.9

Attributes
----------
id: :class:`str`
The region ID, e.g. ``"us-west"``. Use this as the
:attr:`VoiceChannel.rtc_region` of a voice channel.
name: :class:`str`
The region's display name, e.g. ``"US West"``.
optimal: :class:`bool`
Whether the region is optimal for the guild's members.
deprecated: :class:`bool`
Whether the region is deprecated.
custom: :class:`bool`
Whether the region is a custom region.
"""

id: str
name: str
optimal: bool
deprecated: bool
custom: bool
Comment thread
vmphase marked this conversation as resolved.


class GuildRoleCounts(dict[int, int]):
"""A dictionary subclass that maps role IDs to their member counts.

Expand Down Expand Up @@ -1604,7 +1635,7 @@ async def create_voice_channel(
position: int = MISSING,
bitrate: int = MISSING,
user_limit: int = MISSING,
rtc_region: VoiceRegion | None = MISSING,
rtc_region: VoiceRegion | str | None = MISSING,
video_quality_mode: VideoQualityMode = MISSING,
overwrites: dict[Role | Member, PermissionOverwrite] = MISSING,
slowmode_delay: int = MISSING,
Expand All @@ -1631,10 +1662,14 @@ async def create_voice_channel(
The channel's preferred audio bitrate in bits per second.
user_limit: :class:`int`
The channel's limit for number of members that can be in a voice channel.
rtc_region: Optional[:class:`VoiceRegion`]
The region for the voice channel's voice communication.
rtc_region: Optional[Union[:class:`str`, :class:`VoiceRegion`]]
Comment thread
vmphase marked this conversation as resolved.
The region ID for the voice channel's voice communication.
A value of ``None`` indicates automatic voice region detection.

.. versionchanged:: 2.9
Comment thread
vmphase marked this conversation as resolved.

A region ID string is now accepted.

.. versionadded:: 1.7
video_quality_mode: :class:`VideoQualityMode`
The camera video quality for the voice channel's participants.
Expand Down Expand Up @@ -1715,7 +1750,7 @@ async def create_stage_channel(
reason: str | None = None,
bitrate: int = MISSING,
user_limit: int = MISSING,
rtc_region: VoiceRegion | None = MISSING,
rtc_region: VoiceRegion | str | None = MISSING,
video_quality_mode: VideoQualityMode = MISSING,
slowmode_delay: int = MISSING,
nsfw: bool = MISSING,
Expand Down Expand Up @@ -1754,10 +1789,14 @@ async def create_stage_channel(

.. versionadded:: 2.7

rtc_region: Optional[:class:`VoiceRegion`]
The region for the voice channel's voice communication.
rtc_region: Optional[Union[:class:`str`, :class:`VoiceRegion`]]
The region ID for the voice channel's voice communication.
A value of ``None`` indicates automatic voice region detection.

.. versionchanged:: 2.9

A :class:`VoiceRegion` member is now accepted.

Comment thread
vmphase marked this conversation as resolved.
.. versionadded:: 2.7

video_quality_mode: :class:`VideoQualityMode`
Expand Down Expand Up @@ -3794,6 +3833,38 @@ async def vanity_invite(self) -> Invite | None:
channel=channel,
)

async def fetch_voice_regions(self) -> list[VoiceServerRegion]:
"""|coro|

Retrieves the voice regions that the guild has access to.

The list of voice regions is dynamic, so this method is the
recommended way to get the currently available regions.

.. versionadded:: 2.9

Returns
-------
List[:class:`VoiceServerRegion`]
The list of voice regions the guild has access to.

Raises
------
HTTPException
Retrieving the voice regions failed.
"""
regions = await self._state.http.get_guild_voice_regions(self.id)
return [
VoiceServerRegion(
id=region.get("id", ""),
name=region.get("name", ""),
optimal=region.get("optimal", False),
deprecated=region.get("deprecated", False),
custom=region.get("custom", False),
)
for region in regions
]

# TODO: use MISSING when async iterators get refactored
def audit_logs(
self,
Expand Down
12 changes: 12 additions & 0 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
)
from .types.snowflake import Snowflake, SnowflakeList
from .types.soundboard import SoundboardSound as SoundboardSoundPayload
from .types.voice import VoiceRegion as VoiceRegionPayload

T = TypeVar("T")
BE = TypeVar("BE", bound=BaseException)
Expand Down Expand Up @@ -1043,6 +1044,17 @@ def guild_voice_state(

return self.request(r, json=payload, reason=reason)

def get_guild_voice_regions(
self,
guild_id: Snowflake,
) -> Response[list[VoiceRegionPayload]]:
return self.request(
Route("GET", "/guilds/{guild_id}/regions", guild_id=guild_id)
)

def get_voice_regions(self) -> Response[list[VoiceRegionPayload]]:
return self.request(Route("GET", "/voice/regions"))

def edit_profile(self, payload: dict[str, Any]) -> Response[user.User]:
return self.request(Route("PATCH", "/users/@me"), json=payload)

Expand Down
1 change: 0 additions & 1 deletion discord/types/voice.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ class VoiceState(TypedDict):
class VoiceRegion(TypedDict):
id: str
name: str
vip: bool
optimal: bool
deprecated: bool
custom: bool
Expand Down
Loading
Loading