mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-06-06 11:57:17 +00:00
Add support for file/attachment descriptions
This commit is contained in:
parent
7f54c45886
commit
08bee0eeb6
@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Optional, TYPE_CHECKING, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
@ -62,14 +62,13 @@ class File:
|
|||||||
a string then the ``filename`` will default to the string given.
|
a string then the ``filename`` will default to the string given.
|
||||||
spoiler: :class:`bool`
|
spoiler: :class:`bool`
|
||||||
Whether the attachment is a spoiler.
|
Whether the attachment is a spoiler.
|
||||||
|
description: Optional[:class:`str`]
|
||||||
|
The file description to display, currently only supported for images.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('fp', 'filename', 'spoiler', '_original_pos', '_owner', '_closer')
|
__slots__ = ('fp', 'filename', 'spoiler', 'description', '_original_pos', '_owner', '_closer')
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
fp: io.BufferedIOBase
|
|
||||||
filename: Optional[str]
|
|
||||||
spoiler: bool
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -77,11 +76,12 @@ class File:
|
|||||||
filename: Optional[str] = None,
|
filename: Optional[str] = None,
|
||||||
*,
|
*,
|
||||||
spoiler: bool = False,
|
spoiler: bool = False,
|
||||||
|
description: Optional[str] = None,
|
||||||
):
|
):
|
||||||
if isinstance(fp, io.IOBase):
|
if isinstance(fp, io.IOBase):
|
||||||
if not (fp.seekable() and fp.readable()):
|
if not (fp.seekable() and fp.readable()):
|
||||||
raise ValueError(f'File buffer {fp!r} must be seekable and 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._original_pos = fp.tell()
|
||||||
self._owner = False
|
self._owner = False
|
||||||
else:
|
else:
|
||||||
@ -102,12 +102,13 @@ class File:
|
|||||||
else:
|
else:
|
||||||
self.filename = getattr(fp, 'name', None)
|
self.filename = getattr(fp, 'name', None)
|
||||||
else:
|
else:
|
||||||
self.filename = filename
|
self.filename: Optional[str] = filename
|
||||||
|
|
||||||
if spoiler and self.filename is not None and not self.filename.startswith('SPOILER_'):
|
if spoiler and self.filename is not None and not self.filename.startswith('SPOILER_'):
|
||||||
self.filename = 'SPOILER_' + self.filename
|
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:
|
def reset(self, *, seek: Union[int, bool] = True) -> None:
|
||||||
# The `seek` parameter is needed because
|
# The `seek` parameter is needed because
|
||||||
|
@ -267,7 +267,8 @@ class HTTPClient:
|
|||||||
f.reset(seek=tries)
|
f.reset(seek=tries)
|
||||||
|
|
||||||
if form:
|
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:
|
for params in form:
|
||||||
form_data.add_field(**params)
|
form_data.add_field(**params)
|
||||||
kwargs['data'] = form_data
|
kwargs['data'] = form_data
|
||||||
@ -496,28 +497,31 @@ class HTTPClient:
|
|||||||
payload['components'] = components
|
payload['components'] = components
|
||||||
if stickers:
|
if stickers:
|
||||||
payload['sticker_ids'] = 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)})
|
form.append({'name': 'payload_json', 'value': utils._to_json(payload)})
|
||||||
if len(files) == 1:
|
for index, file in enumerate(files):
|
||||||
file = files[0]
|
|
||||||
form.append(
|
form.append(
|
||||||
{
|
{
|
||||||
'name': 'file',
|
'name': f'files[{index}]',
|
||||||
'value': file.fp,
|
'value': file.fp,
|
||||||
'filename': file.filename,
|
'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)
|
return self.request(route, form=form, files=files)
|
||||||
|
|
||||||
|
@ -151,9 +151,13 @@ class Attachment(Hashable):
|
|||||||
The attachment's `media type <https://en.wikipedia.org/wiki/Media_type>`_
|
The attachment's `media type <https://en.wikipedia.org/wiki/Media_type>`_
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
.. 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):
|
def __init__(self, *, data: AttachmentPayload, state: ConnectionState):
|
||||||
self.id: int = int(data['id'])
|
self.id: int = int(data['id'])
|
||||||
@ -165,6 +169,7 @@ class Attachment(Hashable):
|
|||||||
self.proxy_url: str = data.get('proxy_url')
|
self.proxy_url: str = data.get('proxy_url')
|
||||||
self._http = state.http
|
self._http = state.http
|
||||||
self.content_type: Optional[str] = data.get('content_type')
|
self.content_type: Optional[str] = data.get('content_type')
|
||||||
|
self.description: Optional[str] = data.get('description')
|
||||||
|
|
||||||
def is_spoiler(self) -> bool:
|
def is_spoiler(self) -> bool:
|
||||||
""":class:`bool`: Whether this attachment contains a spoiler."""
|
""":class:`bool`: Whether this attachment contains a spoiler."""
|
||||||
@ -318,6 +323,8 @@ class Attachment(Hashable):
|
|||||||
result['width'] = self.width
|
result['width'] = self.width
|
||||||
if self.content_type:
|
if self.content_type:
|
||||||
result['content_type'] = self.content_type
|
result['content_type'] = self.content_type
|
||||||
|
if self.description is not None:
|
||||||
|
result['description'] = self.description
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ class Reaction(TypedDict):
|
|||||||
class _AttachmentOptional(TypedDict, total=False):
|
class _AttachmentOptional(TypedDict, total=False):
|
||||||
height: Optional[int]
|
height: Optional[int]
|
||||||
width: Optional[int]
|
width: Optional[int]
|
||||||
|
description: str
|
||||||
content_type: str
|
content_type: str
|
||||||
spoiler: bool
|
spoiler: bool
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ class AsyncWebhookAdapter:
|
|||||||
file.reset(seek=attempt)
|
file.reset(seek=attempt)
|
||||||
|
|
||||||
if multipart:
|
if multipart:
|
||||||
form_data = aiohttp.FormData()
|
form_data = aiohttp.FormData(quote_fields=False)
|
||||||
for p in multipart:
|
for p in multipart:
|
||||||
form_data.add_field(**p)
|
form_data.add_field(**p)
|
||||||
to_send = form_data
|
to_send = form_data
|
||||||
@ -487,28 +487,32 @@ def handle_message_parameters(
|
|||||||
files = [file]
|
files = [file]
|
||||||
|
|
||||||
if files:
|
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)})
|
multipart.append({'name': 'payload_json', 'value': utils._to_json(payload)})
|
||||||
payload = None
|
payload = None
|
||||||
if len(files) == 1:
|
for index, file in enumerate(files):
|
||||||
file = files[0]
|
|
||||||
multipart.append(
|
multipart.append(
|
||||||
{
|
{
|
||||||
'name': 'file',
|
'name': f'files[{index}]',
|
||||||
'value': file.fp,
|
'value': file.fp,
|
||||||
'filename': file.filename,
|
'filename': file.filename,
|
||||||
'content_type': 'application/octet-stream',
|
'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)
|
return ExecuteWebhookParameters(payload=payload, multipart=multipart, files=files)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user