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: Voice Message Sending #2579

Merged
merged 24 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8459357
Initial Testing For Voice Messages
Icebluewolf Sep 15, 2024
7bad2f7
Add Aiohttp Logging
Icebluewolf Sep 15, 2024
58aad56
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 16, 2024
f4d5624
Merge branch 'master' into voice_messages
plun1331 Sep 17, 2024
83e3aad
Generalize Implementation A Bit More
Icebluewolf Sep 22, 2024
e7b9d28
fix: merge conflicts
Icebluewolf Sep 22, 2024
d8015b6
Add TODO
Icebluewolf Sep 22, 2024
5bec4a8
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2024
d5551cb
Merge branch 'master' into voice_messages
plun1331 Sep 26, 2024
589fc0f
feat!: Replace voice_message kwarg with VoiceMessage subclass of File
Icebluewolf Nov 27, 2024
c2dcc0d
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 27, 2024
1195173
fix: Set Attachment Metadata For Interaction Responses Correctly
Icebluewolf Dec 26, 2024
a977246
chore: Remove Debug Code
Icebluewolf Dec 26, 2024
f629b65
Merge branch 'voice_messages' of https://github.com/Icebluewolf/pycor…
Icebluewolf Dec 26, 2024
19b6c27
chore: Remove Extra Newline
Icebluewolf Dec 26, 2024
1185576
chore: Changelog
Icebluewolf Dec 27, 2024
1802149
Merge branch 'master' into voice_messages
Icebluewolf Dec 27, 2024
3056e8a
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 27, 2024
5ee4c5b
chore: Remove Redundant Changelog
Icebluewolf Dec 27, 2024
5f07853
docs: Grammar Changes
Icebluewolf Dec 27, 2024
899aa57
feat: Add Filename To VoiceMessage
Icebluewolf Dec 27, 2024
138bf64
Update discord/file.py for consistency
Icebluewolf Dec 27, 2024
ed4eb21
Merge branch 'master' into voice_messages
Icebluewolf Dec 28, 2024
c766cb0
Merge branch 'master' into voice_messages
Lulalaby Dec 28, 2024
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
2 changes: 2 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 `VoiceMessage` subclass of `File` to allow voice messages to be sent.
([#2579](https://github.com/Pycord-Development/pycord/pull/2579))

### Fixed

Expand Down
34 changes: 9 additions & 25 deletions discord/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from .context_managers import Typing
from .enums import ChannelType
from .errors import ClientException, InvalidArgument
from .file import File
from .file import File, VoiceMessage
from .flags import MessageFlags
from .invite import Invite
from .iterators import HistoryIterator
Expand Down Expand Up @@ -1569,7 +1569,7 @@ async def send(
flags = MessageFlags(
suppress_embeds=bool(suppress),
suppress_notifications=bool(silent),
).value
)

if stickers is not None:
stickers = [sticker.id for sticker in stickers]
Expand Down Expand Up @@ -1615,27 +1615,7 @@ async def send(
if file is not None:
if not isinstance(file, File):
raise InvalidArgument("file parameter must be File")

try:
data = await state.http.send_files(
channel.id,
files=[file],
allowed_mentions=allowed_mentions,
content=content,
tts=tts,
embed=embed,
embeds=embeds,
nonce=nonce,
enforce_nonce=enforce_nonce,
message_reference=reference,
stickers=stickers,
components=components,
flags=flags,
poll=poll,
)
finally:
file.close()

files = [file]
elif files is not None:
if len(files) > 10:
raise InvalidArgument(
Expand All @@ -1644,6 +1624,10 @@ async def send(
elif not all(isinstance(file, File) for file in files):
raise InvalidArgument("files parameter must be a list of File")

if files is not None:
flags = flags + MessageFlags(
is_voice_message=any(isinstance(f, VoiceMessage) for f in files)
)
try:
data = await state.http.send_files(
channel.id,
Expand All @@ -1658,7 +1642,7 @@ async def send(
message_reference=reference,
stickers=stickers,
components=components,
flags=flags,
flags=flags.value,
poll=poll,
)
finally:
Expand All @@ -1677,7 +1661,7 @@ async def send(
message_reference=reference,
stickers=stickers,
components=components,
flags=flags,
flags=flags.value,
poll=poll,
)

Expand Down
62 changes: 61 additions & 1 deletion discord/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
import os
from typing import TYPE_CHECKING

__all__ = ("File",)
__all__ = (
"File",
"VoiceMessage",
)


class File:
Expand Down Expand Up @@ -89,6 +92,7 @@ def __init__(
description: str | None = None,
spoiler: bool = False,
):

if isinstance(fp, io.IOBase):
if not (fp.seekable() and fp.readable()):
raise ValueError(f"File buffer {fp!r} must be seekable and readable")
Expand Down Expand Up @@ -143,3 +147,59 @@ def close(self) -> None:
self.fp.close = self._closer
if self._owner:
self._closer()


class VoiceMessage(File):
"""A special case of the File class that represents a voice message.

.. versionadded:: 2.7

.. note::

Just like File objects VoiceMessage objects are single use and are not meant to be reused in
Icebluewolf marked this conversation as resolved.
Show resolved Hide resolved
multiple requests.

Attributes
----------
fp: Union[:class:`os.PathLike`, :class:`io.BufferedIOBase`]
A audio file-like object opened in binary mode and read mode
or a filename representing a file in the hard drive to
open.

.. note::

If the file-like object passed is opened via ``open`` then the
modes 'rb' should be used.

To pass binary data, consider usage of ``io.BytesIO``.

filename: Optional[:class:`str`]
The filename to display when uploading to Discord.
If this is not given then it defaults to ``fp.name`` or if ``fp`` is
a string then the ``filename`` will default to the string given.
description: Optional[:class:`str`]
The description of a file, used by Discord to display alternative text on images.
spoiler: :class:`bool`
Whether the attachment is a spoiler.
waveform: Optional[:class:`str`]
The base64 encoded bytearray representing a sampled waveform. Currently only for voice messages
Icebluewolf marked this conversation as resolved.
Show resolved Hide resolved
duration_secs: Optional[:class:`float`]
The duration of the audio file. Currently only for voice messages
Icebluewolf marked this conversation as resolved.
Show resolved Hide resolved
"""

__slots__ = (
"waveform",
"duration_secs",
)

def __init__(
self,
fp: str | bytes | os.PathLike | io.BufferedIOBase,
# filename: str | None = None,
Icebluewolf marked this conversation as resolved.
Show resolved Hide resolved
waveform: str = "",
Icebluewolf marked this conversation as resolved.
Show resolved Hide resolved
duration_secs: float = 0.0,
**kwargs,
):
super().__init__(fp, **kwargs)
self.waveform = waveform
self.duration_secs = duration_secs
37 changes: 23 additions & 14 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
LoginFailure,
NotFound,
)
from .file import VoiceMessage
from .gateway import DiscordClientWebSocketResponse
from .utils import MISSING, warn_deprecated

Expand Down Expand Up @@ -567,13 +568,17 @@ def send_multipart_helper(
attachments = []
form.append({"name": "payload_json"})
for index, file in enumerate(files):
attachments.append(
{
"id": index,
"filename": file.filename,
"description": file.description,
}
)
attachment_info = {
"id": index,
"filename": file.filename,
"description": file.description,
}
if isinstance(file, VoiceMessage):
attachment_info.update(
waveform=file.waveform,
duration_secs=file.duration_secs,
)
attachments.append(attachment_info)
form.append(
{
"name": f"files[{index}]",
Expand Down Expand Up @@ -633,13 +638,17 @@ def edit_multipart_helper(
attachments = []
form.append({"name": "payload_json"})
for index, file in enumerate(files):
attachments.append(
{
"id": index,
"filename": file.filename,
"description": file.description,
}
)
attachment_info = {
"id": index,
"filename": file.filename,
"description": file.description,
}
if isinstance(file, VoiceMessage):
attachment_info.update(
waveform=file.waveform,
duration_secs=file.duration_secs,
)
attachments.append(attachment_info)
form.append(
{
"name": f"files[{index}]",
Expand Down
10 changes: 7 additions & 3 deletions discord/interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
try_enum,
)
from .errors import ClientException, InteractionResponded, InvalidArgument
from .file import File
from .file import File, VoiceMessage
from .flags import MessageFlags
from .guild import Guild
from .member import Member
Expand Down Expand Up @@ -915,8 +915,7 @@ async def send_message(
if content is not None:
payload["content"] = str(content)

if ephemeral:
payload["flags"] = 64
flags = MessageFlags(ephemeral=ephemeral)

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

if any(isinstance(file, VoiceMessage) for file in files):
flags = flags + MessageFlags(is_voice_message=True)

payload["flags"] = flags.value

parent = self._parent
adapter = async_context.get()
http = parent._state.http
Expand Down
51 changes: 34 additions & 17 deletions discord/webhook/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
InvalidArgument,
NotFound,
)
from ..file import VoiceMessage
from ..flags import MessageFlags
from ..http import Route
from ..message import Attachment, Message
Expand Down Expand Up @@ -507,13 +508,17 @@ def create_interaction_response(
attachments = []
files = files or []
for index, file in enumerate(files):
attachments.append(
{
"id": index,
"filename": file.filename,
"description": file.description,
}
)
attachment_info = {
"id": index,
"filename": file.filename,
"description": file.description,
}
if isinstance(file, VoiceMessage):
attachment_info.update(
waveform=file.waveform,
duration_secs=file.duration_secs,
)
attachments.append(attachment_info)
form.append(
{
"name": f"files[{index}]",
Expand All @@ -522,7 +527,7 @@ def create_interaction_response(
"content_type": "application/octet-stream",
}
)
payload["attachments"] = attachments
payload["data"]["attachments"] = attachments
form[0]["value"] = utils._to_json(payload)

route = Route(
Expand Down Expand Up @@ -658,8 +663,10 @@ def handle_message_parameters(
if username:
payload["username"] = username

flags = MessageFlags(suppress_embeds=suppress, ephemeral=ephemeral)
payload["flags"] = flags.value
flags = MessageFlags(
suppress_embeds=suppress,
ephemeral=ephemeral,
)

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

if files:
voice_message = False
for index, file in enumerate(files):
multipart_files.append(
{
Expand All @@ -689,17 +697,26 @@ def handle_message_parameters(
"content_type": "application/octet-stream",
}
)
_attachments.append(
{
"id": index,
"filename": file.filename,
"description": file.description,
}
)
attachment_info = {
"id": index,
"filename": file.filename,
"description": file.description,
}
if isinstance(file, VoiceMessage):
voice_message = True
attachment_info.update(
waveform=file.waveform,
duration_secs=file.duration_secs,
)
_attachments.append(attachment_info)
if voice_message:
flags = flags + MessageFlags(is_voice_message=True)

if _attachments:
payload["attachments"] = _attachments

payload["flags"] = flags.value

if multipart_files:
multipart.append({"name": "payload_json", "value": utils._to_json(payload)})
payload = None
Expand Down
Loading