Skip to content

Commit f8a7c3a

Browse files
Icebluewolfpre-commit-ci[bot]plun1331DorukyumLulalaby
authored
feat: Voice Message Sending (#2579)
Signed-off-by: Ice Wolfy <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: plun1331 <[email protected]> Co-authored-by: Dorukyum <[email protected]> Co-authored-by: Lala Sabathil <[email protected]>
1 parent d08d47a commit f8a7c3a

File tree

6 files changed

+137
-60
lines changed

6 files changed

+137
-60
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ These changes are available on the `master` branch, but have not yet been releas
3838
([#2620](https://github.com/Pycord-Development/pycord/pull/2620))
3939
- Added helper methods to determine the authorizing party of an `Interaction`.
4040
([#2659](https://github.com/Pycord-Development/pycord/pull/2659))
41+
- Added `VoiceMessage` subclass of `File` to allow voice messages to be sent.
42+
([#2579](https://github.com/Pycord-Development/pycord/pull/2579))
4143

4244
### Fixed
4345

discord/abc.py

Lines changed: 9 additions & 25 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
@@ -1569,7 +1569,7 @@ async def send(
15691569
flags = MessageFlags(
15701570
suppress_embeds=bool(suppress),
15711571
suppress_notifications=bool(silent),
1572-
).value
1572+
)
15731573

15741574
if stickers is not None:
15751575
stickers = [sticker.id for sticker in stickers]
@@ -1615,27 +1615,7 @@ async def send(
16151615
if file is not None:
16161616
if not isinstance(file, File):
16171617
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-
1618+
files = [file]
16391619
elif files is not None:
16401620
if len(files) > 10:
16411621
raise InvalidArgument(
@@ -1644,6 +1624,10 @@ async def send(
16441624
elif not all(isinstance(file, File) for file in files):
16451625
raise InvalidArgument("files parameter must be a list of File")
16461626

1627+
if files is not None:
1628+
flags = flags + MessageFlags(
1629+
is_voice_message=any(isinstance(f, VoiceMessage) for f in files)
1630+
)
16471631
try:
16481632
data = await state.http.send_files(
16491633
channel.id,
@@ -1658,7 +1642,7 @@ async def send(
16581642
message_reference=reference,
16591643
stickers=stickers,
16601644
components=components,
1661-
flags=flags,
1645+
flags=flags.value,
16621646
poll=poll,
16631647
)
16641648
finally:
@@ -1677,7 +1661,7 @@ async def send(
16771661
message_reference=reference,
16781662
stickers=stickers,
16791663
components=components,
1680-
flags=flags,
1664+
flags=flags.value,
16811665
poll=poll,
16821666
)
16831667

discord/file.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
import os
3030
from typing import TYPE_CHECKING
3131

32-
__all__ = ("File",)
32+
__all__ = (
33+
"File",
34+
"VoiceMessage",
35+
)
3336

3437

3538
class File:
@@ -89,6 +92,7 @@ def __init__(
8992
description: str | None = None,
9093
spoiler: bool = False,
9194
):
95+
9296
if isinstance(fp, io.IOBase):
9397
if not (fp.seekable() and fp.readable()):
9498
raise ValueError(f"File buffer {fp!r} must be seekable and readable")
@@ -143,3 +147,60 @@ def close(self) -> None:
143147
self.fp.close = self._closer
144148
if self._owner:
145149
self._closer()
150+
151+
152+
class VoiceMessage(File):
153+
"""A special case of the File class that represents a voice message.
154+
155+
.. versionadded:: 2.7
156+
157+
.. note::
158+
159+
Similar to File objects, VoiceMessage objects are single use and are not meant to be reused in
160+
multiple requests.
161+
162+
Attributes
163+
----------
164+
fp: Union[:class:`os.PathLike`, :class:`io.BufferedIOBase`]
165+
A audio file-like object opened in binary mode and read mode
166+
or a filename representing a file in the hard drive to
167+
open.
168+
169+
.. note::
170+
171+
If the file-like object passed is opened via ``open`` then the
172+
modes 'rb' should be used.
173+
174+
To pass binary data, consider usage of ``io.BytesIO``.
175+
176+
filename: Optional[:class:`str`]
177+
The filename to display when uploading to Discord.
178+
If this is not given then it defaults to ``fp.name`` or if ``fp`` is
179+
a string then the ``filename`` will default to the string given.
180+
description: Optional[:class:`str`]
181+
The description of a file, used by Discord to display alternative text on images.
182+
spoiler: :class:`bool`
183+
Whether the attachment is a spoiler.
184+
waveform: Optional[:class:`str`]
185+
The base64 encoded bytearray representing a sampled waveform.
186+
duration_secs: Optional[:class:`float`]
187+
The duration of the voice message.
188+
"""
189+
190+
__slots__ = (
191+
"waveform",
192+
"duration_secs",
193+
)
194+
195+
def __init__(
196+
self,
197+
fp: str | bytes | os.PathLike | io.BufferedIOBase,
198+
filename: str | None = None,
199+
*,
200+
waveform: str = "",
201+
duration_secs: float = 0.0,
202+
**kwargs,
203+
):
204+
super().__init__(fp, filename, **kwargs)
205+
self.waveform = waveform
206+
self.duration_secs = duration_secs

discord/http.py

Lines changed: 23 additions & 14 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

@@ -567,13 +568,17 @@ def send_multipart_helper(
567568
attachments = []
568569
form.append({"name": "payload_json"})
569570
for index, file in enumerate(files):
570-
attachments.append(
571-
{
572-
"id": index,
573-
"filename": file.filename,
574-
"description": file.description,
575-
}
576-
)
571+
attachment_info = {
572+
"id": index,
573+
"filename": file.filename,
574+
"description": file.description,
575+
}
576+
if isinstance(file, VoiceMessage):
577+
attachment_info.update(
578+
waveform=file.waveform,
579+
duration_secs=file.duration_secs,
580+
)
581+
attachments.append(attachment_info)
577582
form.append(
578583
{
579584
"name": f"files[{index}]",
@@ -633,13 +638,17 @@ def edit_multipart_helper(
633638
attachments = []
634639
form.append({"name": "payload_json"})
635640
for index, file in enumerate(files):
636-
attachments.append(
637-
{
638-
"id": index,
639-
"filename": file.filename,
640-
"description": file.description,
641-
}
642-
)
641+
attachment_info = {
642+
"id": index,
643+
"filename": file.filename,
644+
"description": file.description,
645+
}
646+
if isinstance(file, VoiceMessage):
647+
attachment_info.update(
648+
waveform=file.waveform,
649+
duration_secs=file.duration_secs,
650+
)
651+
attachments.append(attachment_info)
643652
form.append(
644653
{
645654
"name": f"files[{index}]",

discord/interactions.py

Lines changed: 7 additions & 3 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
@@ -957,8 +957,7 @@ async def send_message(
957957
if content is not None:
958958
payload["content"] = str(content)
959959

960-
if ephemeral:
961-
payload["flags"] = 64
960+
flags = MessageFlags(ephemeral=ephemeral)
962961

963962
if view is not None:
964963
payload["components"] = view.to_components()
@@ -996,6 +995,11 @@ async def send_message(
996995
elif not all(isinstance(file, File) for file in files):
997996
raise InvalidArgument("files parameter must be a list of File")
998997

998+
if any(isinstance(file, VoiceMessage) for file in files):
999+
flags = flags + MessageFlags(is_voice_message=True)
1000+
1001+
payload["flags"] = flags.value
1002+
9991003
parent = self._parent
10001004
adapter = async_context.get()
10011005
http = parent._state.http

discord/webhook/async_.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
InvalidArgument,
4848
NotFound,
4949
)
50+
from ..file import VoiceMessage
5051
from ..flags import MessageFlags
5152
from ..http import Route
5253
from ..message import Attachment, Message
@@ -507,13 +508,17 @@ def create_interaction_response(
507508
attachments = []
508509
files = files or []
509510
for index, file in enumerate(files):
510-
attachments.append(
511-
{
512-
"id": index,
513-
"filename": file.filename,
514-
"description": file.description,
515-
}
516-
)
511+
attachment_info = {
512+
"id": index,
513+
"filename": file.filename,
514+
"description": file.description,
515+
}
516+
if isinstance(file, VoiceMessage):
517+
attachment_info.update(
518+
waveform=file.waveform,
519+
duration_secs=file.duration_secs,
520+
)
521+
attachments.append(attachment_info)
517522
form.append(
518523
{
519524
"name": f"files[{index}]",
@@ -522,7 +527,7 @@ def create_interaction_response(
522527
"content_type": "application/octet-stream",
523528
}
524529
)
525-
payload["attachments"] = attachments
530+
payload["data"]["attachments"] = attachments
526531
form[0]["value"] = utils._to_json(payload)
527532

528533
route = Route(
@@ -658,8 +663,10 @@ def handle_message_parameters(
658663
if username:
659664
payload["username"] = username
660665

661-
flags = MessageFlags(suppress_embeds=suppress, ephemeral=ephemeral)
662-
payload["flags"] = flags.value
666+
flags = MessageFlags(
667+
suppress_embeds=suppress,
668+
ephemeral=ephemeral,
669+
)
663670

664671
if applied_tags is not MISSING:
665672
payload["applied_tags"] = applied_tags
@@ -680,6 +687,7 @@ def handle_message_parameters(
680687
files = [file]
681688

682689
if files:
690+
voice_message = False
683691
for index, file in enumerate(files):
684692
multipart_files.append(
685693
{
@@ -689,17 +697,26 @@ def handle_message_parameters(
689697
"content_type": "application/octet-stream",
690698
}
691699
)
692-
_attachments.append(
693-
{
694-
"id": index,
695-
"filename": file.filename,
696-
"description": file.description,
697-
}
698-
)
700+
attachment_info = {
701+
"id": index,
702+
"filename": file.filename,
703+
"description": file.description,
704+
}
705+
if isinstance(file, VoiceMessage):
706+
voice_message = True
707+
attachment_info.update(
708+
waveform=file.waveform,
709+
duration_secs=file.duration_secs,
710+
)
711+
_attachments.append(attachment_info)
712+
if voice_message:
713+
flags = flags + MessageFlags(is_voice_message=True)
699714

700715
if _attachments:
701716
payload["attachments"] = _attachments
702717

718+
payload["flags"] = flags.value
719+
703720
if multipart_files:
704721
multipart.append({"name": "payload_json", "value": utils._to_json(payload)})
705722
payload = None

0 commit comments

Comments
 (0)