Browse Source

Add support for file/attachment descriptions

pull/7494/head
Josh 3 years ago
committed by GitHub
parent
commit
08bee0eeb6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      discord/file.py
  2. 34
      discord/http.py
  3. 9
      discord/message.py
  4. 1
      discord/types/message.py
  5. 32
      discord/webhook/async_.py

21
discord/file.py

@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE.
"""
from __future__ import annotations
from typing import Optional, TYPE_CHECKING, Union
from typing import Optional, Union
import os
import io
@ -62,14 +62,13 @@ class File:
a string then the ``filename`` will default to the string given.
spoiler: :class:`bool`
Whether the attachment is a spoiler.
"""
description: Optional[:class:`str`]
The file description to display, currently only supported for images.
__slots__ = ('fp', 'filename', 'spoiler', '_original_pos', '_owner', '_closer')
.. versionadded:: 2.0
"""
if TYPE_CHECKING:
fp: io.BufferedIOBase
filename: Optional[str]
spoiler: bool
__slots__ = ('fp', 'filename', 'spoiler', 'description', '_original_pos', '_owner', '_closer')
def __init__(
self,
@ -77,11 +76,12 @@ class File:
filename: Optional[str] = None,
*,
spoiler: bool = False,
description: Optional[str] = None,
):
if isinstance(fp, io.IOBase):
if not (fp.seekable() and fp.readable()):
raise ValueError(f'File buffer {fp!r} must be seekable and readable')
self.fp = fp
self.fp: io.BufferedIOBase = fp
self._original_pos = fp.tell()
self._owner = False
else:
@ -102,12 +102,13 @@ class File:
else:
self.filename = getattr(fp, 'name', None)
else:
self.filename = filename
self.filename: Optional[str] = filename
if spoiler and self.filename is not None and not self.filename.startswith('SPOILER_'):
self.filename = 'SPOILER_' + self.filename
self.spoiler = spoiler or (self.filename is not None and self.filename.startswith('SPOILER_'))
self.spoiler: bool = spoiler or (self.filename is not None and self.filename.startswith('SPOILER_'))
self.description: Optional[str] = description
def reset(self, *, seek: Union[int, bool] = True) -> None:
# The `seek` parameter is needed because

34
discord/http.py

@ -267,7 +267,8 @@ class HTTPClient:
f.reset(seek=tries)
if form:
form_data = aiohttp.FormData()
# with quote_fields=True '[' and ']' in file field names are escaped, which discord does not support
form_data = aiohttp.FormData(quote_fields=False)
for params in form:
form_data.add_field(**params)
kwargs['data'] = form_data
@ -496,28 +497,31 @@ class HTTPClient:
payload['components'] = components
if stickers:
payload['sticker_ids'] = stickers
if files:
attachments = []
for index, file in enumerate(files):
attachment = {
"id": index,
"filename": file.filename,
}
if file.description is not None:
attachment["description"] = file.description
attachments.append(attachment)
payload['attachments'] = attachments
form.append({'name': 'payload_json', 'value': utils._to_json(payload)})
if len(files) == 1:
file = files[0]
for index, file in enumerate(files):
form.append(
{
'name': 'file',
'name': f'files[{index}]',
'value': file.fp,
'filename': file.filename,
'content_type': 'application/octet-stream',
'content_type': 'image/png',
}
)
else:
for index, file in enumerate(files):
form.append(
{
'name': f'file{index}',
'value': file.fp,
'filename': file.filename,
'content_type': 'application/octet-stream',
}
)
return self.request(route, form=form, files=files)

9
discord/message.py

@ -151,9 +151,13 @@ class Attachment(Hashable):
The attachment's `media type <https://en.wikipedia.org/wiki/Media_type>`_
.. versionadded:: 1.7
description: Optional[:class:`str`]
The attachment's description. Only applicable to images.
.. versionadded:: 2.0
"""
__slots__ = ('id', 'size', 'height', 'width', 'filename', 'url', 'proxy_url', '_http', 'content_type')
__slots__ = ('id', 'size', 'height', 'width', 'filename', 'url', 'proxy_url', '_http', 'content_type', 'description')
def __init__(self, *, data: AttachmentPayload, state: ConnectionState):
self.id: int = int(data['id'])
@ -165,6 +169,7 @@ class Attachment(Hashable):
self.proxy_url: str = data.get('proxy_url')
self._http = state.http
self.content_type: Optional[str] = data.get('content_type')
self.description: Optional[str] = data.get('description')
def is_spoiler(self) -> bool:
""":class:`bool`: Whether this attachment contains a spoiler."""
@ -318,6 +323,8 @@ class Attachment(Hashable):
result['width'] = self.width
if self.content_type:
result['content_type'] = self.content_type
if self.description is not None:
result['description'] = self.description
return result

1
discord/types/message.py

@ -52,6 +52,7 @@ class Reaction(TypedDict):
class _AttachmentOptional(TypedDict, total=False):
height: Optional[int]
width: Optional[int]
description: str
content_type: str
spoiler: bool

32
discord/webhook/async_.py

@ -142,7 +142,7 @@ class AsyncWebhookAdapter:
file.reset(seek=attempt)
if multipart:
form_data = aiohttp.FormData()
form_data = aiohttp.FormData(quote_fields=False)
for p in multipart:
form_data.add_field(**p)
to_send = form_data
@ -487,28 +487,32 @@ def handle_message_parameters(
files = [file]
if files:
for index, file in enumerate(files):
attachments = []
for index, file in enumerate(files):
attachment = {
"id": index,
"filename": file.filename,
}
if file.description is not None:
attachment["description"] = file.description
attachments.append(attachment)
payload['attachments'] = attachments
multipart.append({'name': 'payload_json', 'value': utils._to_json(payload)})
payload = None
if len(files) == 1:
file = files[0]
for index, file in enumerate(files):
multipart.append(
{
'name': 'file',
'name': f'files[{index}]',
'value': file.fp,
'filename': file.filename,
'content_type': 'application/octet-stream',
}
)
else:
for index, file in enumerate(files):
multipart.append(
{
'name': f'file{index}',
'value': file.fp,
'filename': file.filename,
'content_type': 'application/octet-stream',
}
)
return ExecuteWebhookParameters(payload=payload, multipart=multipart, files=files)

Loading…
Cancel
Save