feat(file): Add support for sending file in message

This commit is contained in:
grey-cat-1908 2022-04-22 19:42:18 +03:00
parent 270792f803
commit 96cbe5ea0e
3 changed files with 120 additions and 8 deletions

View file

@ -197,7 +197,7 @@ class HTTPClient:
Optional[:class:`Dict`]
JSON response from the Discord API.
"""
return await self.__send("POST", route, json=data, headers=headers)
return await self.__send("POST", route, data=data, headers=headers)
async def delete(self, route: str, *, headers: dict = None) -> Optional[Dict]:
"""|coro|

View file

@ -17,6 +17,7 @@ from typing import (
TYPE_CHECKING,
)
from ..message.file import File, create_form
from ..message.message import Message
from ...exceptions import EmbedFieldError
from ...models.message.embed import Embed
@ -511,7 +512,13 @@ class MessageableChannel(Channel):
)
async def send(
self, content: str = None, *, embed: Embed = None, embeds: List[Embed] = None
self,
content: str = None,
*,
embed: Embed = None,
embeds: List[Embed] = None,
file: File = None,
files: List[File] = None,
) -> Message:
"""|coro|
@ -527,6 +534,10 @@ class MessageableChannel(Channel):
Embed
embeds: Optional[List[:class:`~melisa.models.message.embed.Embed`]]
List of embeds
file: Optional[:class:`~melisa.models.message.file.File`]
File
files: Optional[List[:class:`~melisa.models.message.file.File`]]
List of files
Raises
-------
@ -539,14 +550,14 @@ class MessageableChannel(Channel):
"""
# ToDo: Add other parameters
# ToDo: add file checks
if embeds is None:
embeds = []
embeds = [embed.to_dict()] if embed is not None else []
if files is None:
files = [file] if file is not None else []
content = str(content) if content is not None else None
if embed is not None:
embeds.append(embed.to_dict())
payload = {"content": str(content) if content is not None else None}
for _embed in embeds:
if embed.total_length() > 6000:
@ -554,10 +565,17 @@ class MessageableChannel(Channel):
"Embed", embed.total_length(), 6000
)
payload["embeds"] = embeds
print(create_form(payload, files))
content_type, data = create_form(payload, files)
return Message.from_dict(
await self._http.post(
f"/channels/{self.id}/messages",
data={"content": content, "embeds": embeds},
data=data,
headers={"Content-Type": content_type}
)
)

View file

@ -0,0 +1,94 @@
# Copyright MelisaDev 2022 - Present
# Full MIT License can be found in `LICENSE.txt` at the project root.
from __future__ import annotations
import io
import os
from typing import Union, Dict, Any, List, Tuple
from aiohttp import FormData, Payload
import melisa.utils.json as json
def create_form(payload: Dict[str, Any], files: List[File]):
"""
Creates an aiohttp payload from an array of File objects.
"""
form = FormData()
form.add_field("payload_json", json.dumps(payload))
for index, file in enumerate(files):
form.add_field(
"file",
file.filepath,
filename=file.filename,
content_type="application/octet-stream",
)
payload = form()
return payload.headers['Content-Type'], payload
class File:
"""
A parameter object used for sending file objects.
Attributes
----------
filepath: Union[:class:`os.PathLike`, :class:`io.BufferedIOBase`]
A 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 ``filepath`` is
a string then the ``filename`` will default to the string given.
spoiler: :class:`bool`
Whether the attachment is a spoiler.
"""
def __init__(
self,
filepath: Union[str, bytes, os.PathLike, io.BufferedIOBase],
*,
filename: str = None,
spoiler: bool = False,
):
# Some features are from the discord.py lib, thanks discord.py devs.
if isinstance(filepath, io.IOBase):
if not (filepath.seekable() and filepath.readable()):
raise ValueError(
f"File buffer {filepath!r} must be seekable and readable"
)
self.filepath = filepath
self._owner = False
else:
self.filepath = open(filepath, "rb")
self._owner = True
if filename is None:
if isinstance(filepath, str):
_, self.filename = os.path.split(filepath)
else:
self.filename = getattr(filepath, "name", None)
else:
self.filename = filename
self.spoiler = spoiler or (
self.filename is not None and self.filename.startswith("SPOILER_")
)
if self.spoiler:
self.filename = "SPOILER_" + filename
def close(self):
if self._owner:
self.filepath.close()