Passed
Push — main ( 2a237c...d48b11 )
by Yohann
02:04 queued 11s
created

pincer.objects.message.message   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 183
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 19
eloc 91
dl 0
loc 183
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
C Message.__post_init__() 0 26 9
A Message.isempty() 0 8 1
A Message.to_dict() 0 25 3
A Message.serialize() 0 42 5
A Message.__str__() 0 2 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 json import dumps
8
from typing import TYPE_CHECKING
9
10
from aiohttp import FormData
11
12
from ..message.file import File
13
from ...exceptions import CommandReturnIsEmpty
14
15
if TYPE_CHECKING:
16
    from typing import Dict, Union, List, Optional, Tuple
17
18
    from aiohttp import Payload
19
20
    from .embed import Embed
21
    from .component import MessageComponent
22
    from ..app.interactions import InteractionFlags
23
    from ..message.user_message import AllowedMentions
24
    from ...objects.app import CallbackType
25
26
PILLOW_IMPORT = True
27
28
try:
29
    from PIL.Image import Image
30
except (ModuleNotFoundError, ImportError):
31
    PILLOW_IMPORT = False
32
33
34
@dataclass(repr=False)
35
class Message:
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
36
    """A discord message that will be send to discord
37
38
    Attributes
39
    ----------
40
    content: :class:`str`
41
        The text in the message.
42
        |default| ``""``
43
    attachments: Optional[List[:class:`~pincer.objects.message.file.File`]]
44
        Attachments on the message. This is a File object. You can also attach
45
        a Pillow Image or string. Pillow images will be converted to PNGs. They
46
        will use the naming scheme ``image%`` where % is the images index in the
47
        attachments array. Strings will be read as a filepath. The name of the
48
        file that the string points to will be used as the name.
49
    tts: Optional[:class:`bool`]
50
        Whether the message should be spoken to the user.
51
        |default| :data:`False`
52
    embeds: Optional[List[:class:`~pincer.objects.message.embed.Embed`]]
53
        Embed attached to the message. This is an Embed object.
54
    allowed_mentions: Optional[:class:`~pincer.objects.message.message.AllowedMentions`]
55
        The allowed mentions for the message.
56
    components: Optional[List[:class:`~pincer.objects.message.component.MessageComponent`]]
57
        The components of the message.
58
    flags: Optional[:class:`~pincer.objects.app.interactions.InteractionFlags`]
59
        The interaction flags for the message.
60
    type: Optional[:class:`~pincer.objects.app.interaction_base.CallbackType`]
61
        The type of the callback.
62
    """
63
    # noqa: E501
64
65
    content: str = ''
66
    attachments: Optional[List[File]] = None
67
    tts: Optional[bool] = False
68
    embeds: Optional[List[Embed]] = None
0 ignored issues
show
introduced by
The variable Embed does not seem to be defined in case TYPE_CHECKING on line 15 is False. Are you sure this can never be the case?
Loading history...
69
    allowed_mentions: Optional[AllowedMentions] = None
0 ignored issues
show
introduced by
The variable AllowedMentions does not seem to be defined in case TYPE_CHECKING on line 15 is False. Are you sure this can never be the case?
Loading history...
70
    components: Optional[List[MessageComponent]] = None
0 ignored issues
show
introduced by
The variable MessageComponent does not seem to be defined in case TYPE_CHECKING on line 15 is False. Are you sure this can never be the case?
Loading history...
71
    flags: Optional[InteractionFlags] = None
0 ignored issues
show
introduced by
The variable InteractionFlags does not seem to be defined in case TYPE_CHECKING on line 15 is False. Are you sure this can never be the case?
Loading history...
72
    delete_after: Optional[float] = None
73
74
    def __post_init__(self):
75
        if self.delete_after and self.delete_after < 0:
76
            raise ValueError(
77
                "Message can not be deleted after a negative amount of "
78
                "seconds!"
79
            )
80
81
        if not self.attachments:
82
            return
83
84
        attachment = []
85
86
        for count, value in enumerate(self.attachments):
87
            if isinstance(value, File):
88
                attachment.append(value)
89
            elif PILLOW_IMPORT and isinstance(value, Image):
90
                attachment.append(File.from_pillow_image(
91
                    value,
92
                    f"image{count}.png",
93
                ))
94
            elif isinstance(value, str):
95
                attachment.append(File.from_file(value))
96
            else:
97
                raise ValueError(f"Attachment {count} is invalid type.")
98
99
        self.attachments = attachment
100
101
    @property
102
    def isempty(self) -> bool:
103
        """:class:`bool`: If the message is empty."""
104
105
        return (
106
            len(self.content) < 1
107
            and not self.embeds
108
            and not self.attachments
109
        )
110
111
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
112
113
        allowed_mentions = (
114
            self.allowed_mentions.to_dict()
115
            if self.allowed_mentions else {}
116
        )
117
118
        # Attachments aren't serialized
119
        # because they are not sent as part of the json
120
        resp = {
121
            "content": self.content,
122
            "tts": self.tts,
123
            "flags": self.flags,
124
            "embeds": [embed.to_dict() for embed in (self.embeds or [])],
125
            "allowed_mentions": allowed_mentions,
126
            "components": [
127
                components.to_dict() for components in (self.components or [])
128
            ]
129
        }
130
131
        # IDE does not recognise return type of filter properly.
132
        # noinspection PyTypeChecker
133
        return dict(filter(
134
            lambda kv: kv[1],
135
            resp.items()
136
        ))
137
138
    def serialize(
139
        self, message_type: Optional[CallbackType] = None
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 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 = {
165
                "data": json_data,
166
                "type": message_type
167
            }
168
169
        if not self.attachments:
170
            return "application/json", json_payload
171
172
        form = FormData()
173
        form.add_field("payload_json", dumps(json_payload))
174
175
        for file in self.attachments:
176
            form.add_field("file", file.content, filename=file.filename)
177
178
        payload = form()
179
        return payload.headers["Content-Type"], payload
180
181
    def __str__(self):
182
        return self.content
183