Message.__post_init__()   C
last analyzed

Complexity

Conditions 9

Size

Total Lines 28
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 28
rs 6.6666
c 0
b 0
f 0
cc 9
nop 1
1
# Copyright Pincer 2021-Present
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
# Full MIT License can be found in `LICENSE` at the project root.
3
4
from __future__ import annotations
5
6
from dataclasses import dataclass
7
from typing import TYPE_CHECKING
8
9
from ..message.file import File, create_form
10
from ...exceptions import CommandReturnIsEmpty
11
12
if TYPE_CHECKING:
13
    from typing import Dict, Union, List, Optional, Tuple
14
15
    from aiohttp import Payload
0 ignored issues
show
introduced by
Unable to import 'aiohttp'
Loading history...
16
17
    from .embed import Embed
18
    from .component import MessageComponent
19
    from ..app.interactions import InteractionFlags
20
    from ..message.user_message import AllowedMentions
21
    from ...objects.app import CallbackType
22
23
PILLOW_IMPORT = True
24
25
try:
26
    from PIL.Image import Image
27
except (ModuleNotFoundError, ImportError):
28
    PILLOW_IMPORT = False
29
30
31
@dataclass(repr=False)
32
class Message:
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
33
    """A discord message that will be sent to discord
34
35
    Attributes
36
    ----------
37
    content: :class:`str`
38
        The text in the message.
39
        |default| ``""``
40
    attachments: Optional[List[:class:`~pincer.objects.message.file.File`]]
41
        Attachments on the message. This is a File object. You can also attach
42
        a Pillow Image or string. Pillow images will be converted to PNGs. They
43
        will use the naming scheme ``image%`` where % is the images index in the
44
        attachments array. Strings will be read as a filepath. The name of the
45
        file that the string points to will be used as the name.
46
    tts: Optional[:class:`bool`]
47
        Whether the message should be spoken to the user.
48
        |default| :data:`False`
49
    embeds: Optional[List[:class:`~pincer.objects.message.embed.Embed`]]
50
        Embed attached to the message. This is an Embed object.
51
    allowed_mentions: Optional[:class:`~pincer.objects.message.message.AllowedMentions`]
52
        The allowed mentions for the message.
53
    components: Optional[List[:class:`~pincer.objects.message.component.MessageComponent`]]
54
        The components of the message.
55
    flags: Optional[:class:`~pincer.objects.app.interactions.InteractionFlags`]
56
        The interaction flags for the message.
57
    type: Optional[:class:`~pincer.objects.app.interaction_base.CallbackType`]
58
        The type of the callback.
59
    """  # noqa: E501
60
61
    content: Optional[str] = None
62
    attachments: Optional[List[File]] = None
63
    tts: Optional[bool] = None
64
    embeds: Optional[List[Embed]] = None
65
    allowed_mentions: Optional[AllowedMentions] = None
66
    components: Optional[List[MessageComponent]] = None
67
    flags: Optional[InteractionFlags] = None
68
    delete_after: Optional[float] = None
69
70
    def __post_init__(self):
71
        if self.delete_after and self.delete_after < 0:
72
            raise ValueError(
73
                "Message can not be deleted after a negative amount of "
74
                "seconds!"
75
            )
76
77
        if not self.attachments:
78
            return
79
80
        attachment = []
81
82
        for count, value in enumerate(self.attachments):
83
            if isinstance(value, File):
84
                attachment.append(value)
85
            elif PILLOW_IMPORT and isinstance(value, Image):
86
                attachment.append(
87
                    File.from_pillow_image(
88
                        value,
89
                        f"image{count}.png",
90
                    )
91
                )
92
            elif isinstance(value, str):
93
                attachment.append(File.from_file(value))
94
            else:
95
                raise ValueError(f"Attachment {count} is invalid type.")
96
97
        self.attachments = attachment
98
99
    @property
100
    def isempty(self) -> bool:
101
        """:class:`bool`: If the message is empty."""
102
103
        return (
104
            (not self.content or not self.content.strip())
105
            and not self.embeds
106
            and not self.attachments
107
        )
108
109
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
110
111
        allowed_mentions = (
112
            self.allowed_mentions.to_dict() if self.allowed_mentions else {}
113
        )
114
115
        # Attachments aren't serialized
116
        # because they are not sent as part of the json
117
        resp = {
118
            "content": self.content,
119
            "tts": self.tts,
120
            "flags": self.flags,
121
            "embeds": [embed.to_dict() for embed in self.embeds]
122
            if self.embeds is not None
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 10 spaces).
Loading history...
123
            else None,
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 10 spaces).
Loading history...
124
            "allowed_mentions": allowed_mentions,
125
            "components": [
126
                components.to_dict() for components in self.components
127
            ]
128
            if self.components is not None
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 14 spaces).
Loading history...
129
            else None,
0 ignored issues
show
Coding Style introduced by
Wrong continued indentation (add 14 spaces).
Loading history...
130
        }
131
132
        # IDE does not recognise return type of filter properly.
133
        # noinspection PyTypeChecker
134
        return dict(filter(lambda kv: kv[1] is not None, resp.items()))
135
136
    def serialize(
137
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
138
        message_type: Optional[CallbackType] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
139
        allow_empty: bool = False,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
140
    ) -> Tuple[str, Union[Payload, Dict]]:
141
        """
142
        Parameters
143
        ----------
144
        message_type : Optional[:class:`pincer.objects.app.CallbackType`]
145
            Callback type of message.
146
147
        Returns
148
        -------
149
        Tuple[str, Union[Payload, Dict]]
150
            (content_type, data)
151
152
        Raises
153
        ------
154
        :class:`pincer.exceptions.CommandReturnIsEmpty`
155
            Command does not have content, an embed, or attachment.
156
        """
157
        if not allow_empty and self.isempty:
158
            raise CommandReturnIsEmpty("Cannot return empty message.")
159
160
        json_payload = self.to_dict()
161
162
        if message_type is not None:
163
            json_data = json_payload
164
            json_payload = {"data": json_data, "type": message_type}
165
166
        if not self.attachments:
167
            return "application/json", json_payload
168
169
        return create_form(json_payload, self.attachments)
170
171
    def __str__(self):
172
        return self.content
173