Skip to content

Commit 589fc0f

Browse files
committed
feat!: Replace voice_message kwarg with VoiceMessage subclass of File
1 parent d5551cb commit 589fc0f

File tree

5 files changed

+123
-101
lines changed

5 files changed

+123
-101
lines changed

discord/abc.py

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from .context_managers import Typing
4646
from .enums import ChannelType
4747
from .errors import ClientException, InvalidArgument
48-
from .file import File
48+
from .file import File, VoiceMessage
4949
from .flags import MessageFlags
5050
from .invite import Invite
5151
from .iterators import HistoryIterator
@@ -1440,7 +1440,6 @@ async def send(
14401440
poll=None,
14411441
suppress=None,
14421442
silent=None,
1443-
voice_message=None,
14441443
):
14451444
"""|coro|
14461445
@@ -1568,8 +1567,7 @@ async def send(
15681567
flags = MessageFlags(
15691568
suppress_embeds=bool(suppress),
15701569
suppress_notifications=bool(silent),
1571-
is_voice_message=bool(voice_message),
1572-
).value
1570+
)
15731571

15741572
if stickers is not None:
15751573
stickers = [sticker.id for sticker in stickers]
@@ -1615,27 +1613,7 @@ async def send(
16151613
if file is not None:
16161614
if not isinstance(file, File):
16171615
raise InvalidArgument("file parameter must be File")
1618-
1619-
try:
1620-
data = await state.http.send_files(
1621-
channel.id,
1622-
files=[file],
1623-
allowed_mentions=allowed_mentions,
1624-
content=content,
1625-
tts=tts,
1626-
embed=embed,
1627-
embeds=embeds,
1628-
nonce=nonce,
1629-
enforce_nonce=enforce_nonce,
1630-
message_reference=reference,
1631-
stickers=stickers,
1632-
components=components,
1633-
flags=flags,
1634-
poll=poll,
1635-
)
1636-
finally:
1637-
file.close()
1638-
1616+
files = [file]
16391617
elif files is not None:
16401618
if len(files) > 10:
16411619
raise InvalidArgument(
@@ -1644,6 +1622,8 @@ async def send(
16441622
elif not all(isinstance(file, File) for file in files):
16451623
raise InvalidArgument("files parameter must be a list of File")
16461624

1625+
if files is not None:
1626+
flags = flags + MessageFlags(is_voice_message=any(isinstance(f, VoiceMessage) for f in files))
16471627
try:
16481628
data = await state.http.send_files(
16491629
channel.id,
@@ -1658,7 +1638,7 @@ async def send(
16581638
message_reference=reference,
16591639
stickers=stickers,
16601640
components=components,
1661-
flags=flags,
1641+
flags=flags.value,
16621642
poll=poll,
16631643
)
16641644
finally:
@@ -1677,7 +1657,7 @@ async def send(
16771657
message_reference=reference,
16781658
stickers=stickers,
16791659
components=components,
1680-
flags=flags,
1660+
flags=flags.value,
16811661
poll=poll,
16821662
)
16831663

discord/file.py

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import os
3030
from typing import TYPE_CHECKING
3131

32-
__all__ = ("File",)
32+
__all__ = ("File", "VoiceMessage", )
3333

3434

3535
class File:
@@ -63,10 +63,6 @@ class File:
6363
The description of a file, used by Discord to display alternative text on images.
6464
spoiler: :class:`bool`
6565
Whether the attachment is a spoiler.
66-
waveform: Optional[:class:`str`]
67-
The base64 encoded bytearray representing a sampled waveform. Currently only for voice messages
68-
duration_secs: Optional[:class:`float`]
69-
The duration of the audio file. Currently only for voice messages
7066
"""
7167

7268
__slots__ = (
@@ -77,8 +73,6 @@ class File:
7773
"_owner",
7874
"_closer",
7975
"description",
80-
"waveform",
81-
"duration_secs",
8276
)
8377

8478
if TYPE_CHECKING:
@@ -94,8 +88,6 @@ def __init__(
9488
*,
9589
description: str | None = None,
9690
spoiler: bool = False,
97-
waveform: str | None = None,
98-
duration_secs: float | None = None,
9991
):
10092

10193
if isinstance(fp, io.IOBase):
@@ -135,8 +127,6 @@ def __init__(
135127
self.filename is not None and self.filename.startswith("SPOILER_")
136128
)
137129
self.description = description
138-
self.waveform = waveform
139-
self.duration_secs = duration_secs
140130

141131
def reset(self, *, seek: int | bool = True) -> None:
142132
# The `seek` parameter is needed because
@@ -154,3 +144,58 @@ def close(self) -> None:
154144
self.fp.close = self._closer
155145
if self._owner:
156146
self._closer()
147+
148+
149+
class VoiceMessage(File):
150+
"""A special case of the File class that represents a voice message.
151+
152+
.. versionadded:: 2.7
153+
154+
.. note::
155+
156+
Just like File objects VoiceMessage objects are single use and are not meant to be reused in
157+
multiple requests.
158+
159+
Attributes
160+
-----------
161+
fp: Union[:class:`os.PathLike`, :class:`io.BufferedIOBase`]
162+
A audio file-like object opened in binary mode and read mode
163+
or a filename representing a file in the hard drive to
164+
open.
165+
166+
.. note::
167+
168+
If the file-like object passed is opened via ``open`` then the
169+
modes 'rb' should be used.
170+
171+
To pass binary data, consider usage of ``io.BytesIO``.
172+
173+
filename: Optional[:class:`str`]
174+
The filename to display when uploading to Discord.
175+
If this is not given then it defaults to ``fp.name`` or if ``fp`` is
176+
a string then the ``filename`` will default to the string given.
177+
description: Optional[:class:`str`]
178+
The description of a file, used by Discord to display alternative text on images.
179+
spoiler: :class:`bool`
180+
Whether the attachment is a spoiler.
181+
waveform: Optional[:class:`str`]
182+
The base64 encoded bytearray representing a sampled waveform. Currently only for voice messages
183+
duration_secs: Optional[:class:`float`]
184+
The duration of the audio file. Currently only for voice messages
185+
"""
186+
187+
__slots__ = (
188+
"waveform",
189+
"duration_secs",
190+
)
191+
192+
def __init__(self,
193+
fp: str | bytes | os.PathLike | io.BufferedIOBase,
194+
# filename: str | None = None,
195+
waveform: str = "",
196+
duration_secs: float = 0.0,
197+
**kwargs
198+
):
199+
super().__init__(fp, **kwargs)
200+
self.waveform = waveform
201+
self.duration_secs = duration_secs

discord/http.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
LoginFailure,
4545
NotFound,
4646
)
47+
from .file import VoiceMessage
4748
from .gateway import DiscordClientWebSocketResponse
4849
from .utils import MISSING, warn_deprecated
4950

@@ -594,15 +595,17 @@ def send_multipart_helper(
594595
attachments = []
595596
form.append({"name": "payload_json"})
596597
for index, file in enumerate(files):
597-
attachments.append(
598-
{
598+
attachment_info = {
599599
"id": index,
600600
"filename": file.filename,
601601
"description": file.description,
602-
"waveform": file.waveform,
603-
"duration_secs": file.duration_secs,
604602
}
605-
)
603+
if isinstance(file, VoiceMessage):
604+
attachment_info.update(
605+
waveform=file.waveform,
606+
duration_secs=file.duration_secs,
607+
)
608+
attachments.append(attachment_info)
606609
form.append(
607610
{
608611
"name": f"files[{index}]",
@@ -662,22 +665,23 @@ def edit_multipart_helper(
662665
attachments = []
663666
form.append({"name": "payload_json"})
664667
for index, file in enumerate(files):
665-
attachments.append(
666-
{
667-
"id": index,
668-
"filename": file.filename,
669-
"description": file.description,
670-
# TODO: Make Editing Work
671-
"waveform": "37WKcJ6jlLSVnaabsbeip4KPmHJXUUEbExgFJE8J7iNPFggpKQkTNl95dobFqqe2tKubnbSTX3yLVVBFS4iqd4dbKmFvMChwfVRKfWFYWRpLaV9jlYtKWWZde6mtnYiDlGNUgmFAWWdRXGNsf2NBYnNcS1uDjm+qwK2urKe8uKqjZ2KGSjtbLUpTO0iDYSBSg6CzCk1LNDVAZnOAvNiUkLu8r8vPnFw6bXZbbXcn0vUU8q2q38Olyfb0y7OhlnV9u6N4zuAH9uI=",
672-
"duration_secs": 60.0,
673-
}
674-
)
668+
attachment_info = {
669+
"id": index,
670+
"filename": file.filename,
671+
"description": file.description,
672+
}
673+
if isinstance(file, VoiceMessage):
674+
attachment_info.update(
675+
waveform=file.waveform,
676+
duration_secs=file.duration_secs,
677+
)
678+
attachments.append(attachment_info)
675679
form.append(
676680
{
677681
"name": f"files[{index}]",
678682
"value": file.fp,
679683
"filename": file.filename,
680-
"content_type": "audio/wav",
684+
"content_type": "application/octet-stream",
681685
}
682686
)
683687
if "attachments" not in payload:

discord/interactions.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
try_enum,
3838
)
3939
from .errors import ClientException, InteractionResponded, InvalidArgument
40-
from .file import File
40+
from .file import File, VoiceMessage
4141
from .flags import MessageFlags
4242
from .guild import Guild
4343
from .member import Member
@@ -840,7 +840,6 @@ async def send_message(
840840
files: list[File] = None,
841841
poll: Poll = None,
842842
delete_after: float = None,
843-
voice_message: bool = False,
844843
) -> Interaction:
845844
"""|coro|
846845
@@ -878,10 +877,6 @@ async def send_message(
878877
The poll to send.
879878
880879
.. versionadded:: 2.6
881-
voice_message: :class:`bool`
882-
If the file should be treated as a voice message.
883-
884-
.. versionadded:: 2.7
885880
886881
Returns
887882
-------
@@ -920,10 +915,7 @@ async def send_message(
920915
if content is not None:
921916
payload["content"] = str(content)
922917

923-
if ephemeral:
924-
payload["flags"] = 64
925-
if voice_message:
926-
payload["flags"] = payload.setdefault("flags", 0) + 8192
918+
flags = MessageFlags(ephemeral=ephemeral)
927919

928920
if view is not None:
929921
payload["components"] = view.to_components()
@@ -961,6 +953,11 @@ async def send_message(
961953
elif not all(isinstance(file, File) for file in files):
962954
raise InvalidArgument("files parameter must be a list of File")
963955

956+
if any(isinstance(file, VoiceMessage) for file in files):
957+
flags = flags + MessageFlags(is_voice_message=True)
958+
959+
payload["flags"] = flags.value
960+
964961
parent = self._parent
965962
adapter = async_context.get()
966963
http = parent._state.http

0 commit comments

Comments
 (0)