Skip to content

Commit ba5ffaf

Browse files
authored
Merge branch 'master' into app_emojis
Signed-off-by: UK <[email protected]>
2 parents 9b018eb + c430fbb commit ba5ffaf

File tree

8 files changed

+128
-51
lines changed

8 files changed

+128
-51
lines changed

CHANGELOG.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ possible (see our [Version Guarantees] for more info).
1010

1111
These changes are available on the `master` branch, but have not yet been released.
1212

13-
⚠️ **This Version Removes Support For Python 3.8** ⚠️
13+
⚠️ **This version removes support for Python 3.8.** ⚠️
1414

1515
### Added
1616

@@ -26,13 +26,27 @@ These changes are available on the `master` branch, but have not yet been releas
2626
([#2501](https://github.com/Pycord-Development/pycord/pull/2501))
2727
- Added `cache_app_emojis` parameter to `Client`.
2828
([#2501](https://github.com/Pycord-Development/pycord/pull/2501))
29+
- Added optional `filter` parameter to `utils.basic_autocomplete()`.
30+
([#2590](https://github.com/Pycord-Development/pycord/pull/2590))
31+
32+
### Fixed
33+
34+
- Fixed `Enum` options not setting the correct type when only one choice is available.
35+
([#2577](https://github.com/Pycord-Development/pycord/pull/2577))
36+
- Fixed `codec` option for `FFmpegOpusAudio` class to make it in line with
37+
documentation. ([#2581](https://github.com/Pycord-Development/pycord/pull/2581))
38+
- Fixed a possible bug where audio would play too fast at the beginning of audio files.
39+
([#2584](https://github.com/Pycord-Development/pycord/pull/2584))
40+
- Fixed the `is_owner()` `user` type hint: `User` -> `User | Member`.
41+
([#2593](https://github.com/Pycord-Development/pycord/pull/2593))
42+
2943

3044
### Changed
3145

3246
- Renamed `cover` property of `ScheduledEvent` and `cover` argument of
3347
`ScheduledEvent.edit` to `image`.
3448
([#2496](https://github.com/Pycord-Development/pycord/pull/2496))
35-
- ⚠️ **This Version Removes Support For Python 3.8** ⚠️
49+
- ⚠️ **Removed support for Python 3.8.**
3650
([#2521](https://github.com/Pycord-Development/pycord/pull/2521))
3751
- `Emoji` has been renamed to `GuildEmoji`.
3852
([#2501](https://github.com/Pycord-Development/pycord/pull/2501))
@@ -42,24 +56,32 @@ These changes are available on the `master` branch, but have not yet been releas
4256
- Deprecated `AppInfo.summary` in favor of `AppInfo.description`.
4357
([#2520](https://github.com/Pycord-Development/pycord/pull/2520))
4458

59+
## [2.6.1] - 2024-09-15
60+
4561
### Fixed
4662

47-
- Fixed `EntitlementIterator` behavior with `limit > 100`.
63+
- Fixed premature garbage collection of tasks.
64+
([#2510](https://github.com/Pycord-Development/pycord/pull/2510))
65+
- Fixed `EntitlementIterator` type hints and behavior with `limit > 100`.
4866
([#2555](https://github.com/Pycord-Development/pycord/pull/2555))
4967
- Fixed missing `stacklevel` parameter in `warn_deprecated` function call inside
5068
`@utils.deprecated`. ([#2500](https://github.com/Pycord-Development/pycord/pull/2500))
51-
- Fixed the typehint in `ConnectionState._polls` to reflect actual behavior, changing it
52-
from `Guild` to `Poll`.
69+
- Fixed the type hint in `ConnectionState._polls` to reflect actual behavior, changing
70+
it from `Guild` to `Poll`.
5371
([#2500](https://github.com/Pycord-Development/pycord/pull/2500))
5472
- Fixed missing `__slots__` attributes in `RawReactionClearEmojiEvent` and
5573
`RawMessagePollVoteEvent`.
5674
([#2500](https://github.com/Pycord-Development/pycord/pull/2500))
5775
- Fixed the type of `ForumChannel.default_sort_order`, changing it from `int` to
5876
`SortOrder`. ([#2500](https://github.com/Pycord-Development/pycord/pull/2500))
59-
- Fixed `PartialMessage`s causing errors when created from `PartialMessageable`.
77+
- Fixed `PartialMessage` causing errors when created from `PartialMessageable`.
6078
([#2568](https://github.com/Pycord-Development/pycord/pull/2500))
6179
- Fixed the `guild` attribute of `Member`s recieved from a `UserCommand` being `None`.
6280
([#2573](https://github.com/Pycord-Development/pycord/pull/2573))
81+
- Fixed `Webhook.send` not including attachment data.
82+
([#2513](https://github.com/Pycord-Development/pycord/pull/2513))
83+
- Fixed inverted type hints in `CheckAnyFailure`.
84+
([#2502](https://github.com/Pycord-Development/pycord/pull/2502))
6385

6486
## [2.6.0] - 2024-07-09
6587

discord/bot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,7 +1407,7 @@ def after_invoke(self, coro):
14071407
self._after_invoke = coro
14081408
return coro
14091409

1410-
async def is_owner(self, user: User) -> bool:
1410+
async def is_owner(self, user: User | Member) -> bool:
14111411
"""|coro|
14121412
14131413
Checks if a :class:`~discord.User` or :class:`~discord.Member` is the owner of
@@ -1422,7 +1422,7 @@ async def is_owner(self, user: User) -> bool:
14221422
14231423
Parameters
14241424
----------
1425-
user: :class:`.abc.User`
1425+
user: Union[:class:`.abc.User`, :class:`.member.Member`]
14261426
The user to check for.
14271427
14281428
Returns

discord/commands/options.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def __init__(
198198
enum_choices = []
199199
input_type_is_class = isinstance(input_type, type)
200200
if input_type_is_class and issubclass(input_type, (Enum, DiscordEnum)):
201-
if description is None:
201+
if description is None and input_type.__doc__ is not None:
202202
description = inspect.cleandoc(input_type.__doc__)
203203
if description and len(description) > 100:
204204
description = description[:97] + "..."
@@ -209,7 +209,9 @@ def __init__(
209209
)
210210
enum_choices = [OptionChoice(e.name, e.value) for e in input_type]
211211
value_class = enum_choices[0].value.__class__
212-
if all(isinstance(elem.value, value_class) for elem in enum_choices):
212+
if value_class in SlashCommandOptionType.__members__ and all(
213+
isinstance(elem.value, value_class) for elem in enum_choices
214+
):
213215
input_type = SlashCommandOptionType.from_datatype(
214216
enum_choices[0].value.__class__
215217
)

discord/player.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -355,8 +355,9 @@ class FFmpegOpusAudio(FFmpegAudio):
355355
The codec to use to encode the audio data. Normally this would be
356356
just ``libopus``, but is used by :meth:`FFmpegOpusAudio.from_probe` to
357357
opportunistically skip pointlessly re-encoding Opus audio data by passing
358-
``copy`` as the codec value. Any values other than ``copy``, ``opus``, or
359-
``libopus`` will be considered ``libopus``. Defaults to ``libopus``.
358+
``copy`` as the codec value. Any values other than ``copy``, or
359+
``libopus`` will be considered ``libopus``. ``opus`` will also be considered
360+
``libopus`` since the ``opus`` encoder is still in development. Defaults to ``libopus``.
360361
361362
.. warning::
362363
@@ -407,7 +408,9 @@ def __init__(
407408
args.append("-i")
408409
args.append("-" if pipe else source)
409410

410-
codec = "copy" if codec in ("opus", "libopus") else "libopus"
411+
# use "libopus" when "opus" is specified since the "opus" encoder is incomplete
412+
# link to ffmpeg docs: https://www.ffmpeg.org/ffmpeg-codecs.html#opus
413+
codec = "copy" if codec == "copy" else "libopus"
411414

412415
args.extend(
413416
(
@@ -417,17 +420,24 @@ def __init__(
417420
"opus",
418421
"-c:a",
419422
codec,
420-
"-ar",
421-
"48000",
422-
"-ac",
423-
"2",
424-
"-b:a",
425-
f"{bitrate}k",
426423
"-loglevel",
427424
"warning",
428425
)
429426
)
430427

428+
# only pass in bitrate, sample rate, channels arguments when actually encoding to avoid ffmpeg warnings
429+
if codec != "copy":
430+
args.extend(
431+
(
432+
"-ar",
433+
"48000",
434+
"-ac",
435+
"2",
436+
"-b:a",
437+
f"{bitrate}k",
438+
)
439+
)
440+
431441
if isinstance(options, str):
432442
args.extend(shlex.split(options))
433443

@@ -501,6 +511,8 @@ def custom_probe(source, executable):
501511

502512
executable = kwargs.get("executable")
503513
codec, bitrate = await cls.probe(source, method=method, executable=executable)
514+
# only re-encode if the source isn't already opus, else directly copy the source audio stream
515+
codec = "copy" if codec in ("opus", "libopus") else "libopus"
504516
return cls(source, bitrate=bitrate, codec=codec, **kwargs) # type: ignore
505517

506518
@classmethod
@@ -717,6 +729,9 @@ def __init__(self, source: AudioSource, client: VoiceClient, *, after=None):
717729
raise TypeError('Expected a callable for the "after" parameter.')
718730

719731
def _do_run(self) -> None:
732+
# attempt to read first audio segment from source before starting
733+
# some sources can take a few seconds and may cause problems
734+
first_data = self.source.read()
720735
self.loops = 0
721736
self._start = time.perf_counter()
722737

@@ -740,7 +755,13 @@ def _do_run(self) -> None:
740755
self._start = time.perf_counter()
741756

742757
self.loops += 1
743-
data = self.source.read()
758+
# Send the data read from the start of the function if it is not None
759+
if first_data is not None:
760+
data = first_data
761+
first_data = None
762+
# Else read the next bit from the source
763+
else:
764+
data = self.source.read()
744765

745766
if not data:
746767
self.stop()

discord/utils.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,9 +1306,12 @@ def generate_snowflake(dt: datetime.datetime | None = None) -> int:
13061306
AV = Awaitable[V]
13071307
Values = Union[V, Callable[[AutocompleteContext], Union[V, AV]], AV]
13081308
AutocompleteFunc = Callable[[AutocompleteContext], AV]
1309+
FilterFunc = Callable[[AutocompleteContext, Any], Union[bool, Awaitable[bool]]]
13091310

13101311

1311-
def basic_autocomplete(values: Values) -> AutocompleteFunc:
1312+
def basic_autocomplete(
1313+
values: Values, *, filter: FilterFunc | None = None
1314+
) -> AutocompleteFunc:
13121315
"""A helper function to make a basic autocomplete for slash commands. This is a pretty standard autocomplete and
13131316
will return any options that start with the value from the user, case-insensitive. If the ``values`` parameter is
13141317
callable, it will be called with the AutocompleteContext.
@@ -1320,18 +1323,21 @@ def basic_autocomplete(values: Values) -> AutocompleteFunc:
13201323
values: Union[Union[Iterable[:class:`.OptionChoice`], Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]], Callable[[:class:`.AutocompleteContext`], Union[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]], Awaitable[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]]], Awaitable[Union[Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]]
13211324
Possible values for the option. Accepts an iterable of :class:`str`, a callable (sync or async) that takes a
13221325
single argument of :class:`.AutocompleteContext`, or a coroutine. Must resolve to an iterable of :class:`str`.
1326+
filter: Optional[Callable[[:class:`.AutocompleteContext`, Any], Union[:class:`bool`, Awaitable[:class:`bool`]]]]
1327+
An optional callable (sync or async) used to filter the autocomplete options. It accepts two arguments:
1328+
the :class:`.AutocompleteContext` and an item from ``values`` iteration treated as callback parameters. If ``None`` is provided, a default filter is used that includes items whose string representation starts with the user's input value, case-insensitive.
1329+
1330+
.. versionadded:: 2.7
13231331
13241332
Returns
13251333
-------
13261334
Callable[[:class:`.AutocompleteContext`], Awaitable[Union[Iterable[:class:`.OptionChoice`], Iterable[:class:`str`], Iterable[:class:`int`], Iterable[:class:`float`]]]]
13271335
A wrapped callback for the autocomplete.
13281336
1329-
Note
1330-
----
1331-
Autocomplete cannot be used for options that have specified choices.
1337+
Examples
1338+
--------
13321339
1333-
Example
1334-
-------
1340+
Basic usage:
13351341
13361342
.. code-block:: python3
13371343
@@ -1344,7 +1350,17 @@ async def autocomplete(ctx):
13441350
13451351
Option(str, "name", autocomplete=basic_autocomplete(autocomplete))
13461352
1353+
With filter parameter:
1354+
1355+
.. code-block:: python3
1356+
1357+
Option(str, "color", autocomplete=basic_autocomplete(("red", "green", "blue"), filter=lambda c, i: str(c.value or "") in i))
1358+
13471359
.. versionadded:: 2.0
1360+
1361+
Note
1362+
----
1363+
Autocomplete cannot be used for options that have specified choices.
13481364
"""
13491365

13501366
async def autocomplete_callback(ctx: AutocompleteContext) -> V:
@@ -1355,11 +1371,23 @@ async def autocomplete_callback(ctx: AutocompleteContext) -> V:
13551371
if asyncio.iscoroutine(_values):
13561372
_values = await _values
13571373

1358-
def check(item: Any) -> bool:
1359-
item = getattr(item, "name", item)
1360-
return str(item).lower().startswith(str(ctx.value or "").lower())
1374+
if filter is None:
1375+
1376+
def _filter(ctx: AutocompleteContext, item: Any) -> bool:
1377+
item = getattr(item, "name", item)
1378+
return str(item).lower().startswith(str(ctx.value or "").lower())
1379+
1380+
gen = (val for val in _values if _filter(ctx, val))
1381+
1382+
elif asyncio.iscoroutinefunction(filter):
1383+
gen = (val for val in _values if await filter(ctx, val))
1384+
1385+
elif callable(filter):
1386+
gen = (val for val in _values if filter(ctx, val))
1387+
1388+
else:
1389+
raise TypeError("``filter`` must be callable.")
13611390

1362-
gen = (val for val in _values if check(val))
13631391
return iter(itertools.islice(gen, 25))
13641392

13651393
return autocomplete_callback

discord/webhook/async_.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -644,8 +644,9 @@ def handle_message_parameters(
644644
payload["embeds"] = [] if embed is None else [embed.to_dict()]
645645
if content is not MISSING:
646646
payload["content"] = str(content) if content is not None else None
647+
_attachments = []
647648
if attachments is not MISSING:
648-
payload["attachments"] = [a.to_dict() for a in attachments]
649+
_attachments = [a.to_dict() for a in attachments]
649650

650651
if view is not MISSING:
651652
payload["components"] = view.to_components() if view is not None else []
@@ -674,32 +675,35 @@ def handle_message_parameters(
674675
payload["allowed_mentions"] = previous_allowed_mentions.to_dict()
675676

676677
multipart = []
678+
multipart_files = []
677679
if file is not MISSING:
678680
files = [file]
679681

680682
if files:
681-
multipart.append({"name": "payload_json", "value": utils._to_json(payload)})
682-
payload = None
683-
if len(files) == 1:
684-
file = files[0]
685-
multipart.append(
683+
for index, file in enumerate(files):
684+
multipart_files.append(
686685
{
687-
"name": "file",
686+
"name": f"files[{index}]",
688687
"value": file.fp,
689688
"filename": file.filename,
690689
"content_type": "application/octet-stream",
691690
}
692691
)
693-
else:
694-
for index, file in enumerate(files):
695-
multipart.append(
696-
{
697-
"name": f"file{index}",
698-
"value": file.fp,
699-
"filename": file.filename,
700-
"content_type": "application/octet-stream",
701-
}
702-
)
692+
_attachments.append(
693+
{
694+
"id": index,
695+
"filename": file.filename,
696+
"description": file.description,
697+
}
698+
)
699+
700+
if _attachments:
701+
payload["attachments"] = _attachments
702+
703+
if multipart_files:
704+
multipart.append({"name": "payload_json", "value": utils._to_json(payload)})
705+
payload = None
706+
multipart += multipart_files
703707

704708
return ExecuteWebhookParameters(payload=payload, multipart=multipart, files=files)
705709

requirements/dev.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
-r _.txt
2-
pylint~=3.2.7
3-
pytest~=8.3.2
2+
pylint~=3.3.1
3+
pytest~=8.3.3
44
pytest-asyncio~=0.23.8
55
# pytest-order~=1.0.1
66
mypy~=1.11.2
77
coverage~=7.6
88
pre-commit==3.8.0
99
codespell==2.3.0
10-
bandit==1.7.9
10+
bandit==1.7.10
1111
flake8==7.1.1

requirements/docs.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ furo==2024.8.6
88
sphinx-autodoc-typehints==2.2.3
99
sphinx-intl==2.2.0
1010
typing_extensions==4.12.2
11-
levenshtein==0.25.1
11+
levenshtein==0.26.0

0 commit comments

Comments
 (0)