Passed
Pull Request — main (#155)
by Oliver
02:04
created

AllowedMentions.to_dict()   A

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 10

Duplication

Lines 12
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 10
dl 12
loc 12
rs 9.9
c 0
b 0
f 0
cc 2
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 json import dumps
7
from typing import TYPE_CHECKING
8
from dataclasses import dataclass
9
10
from aiohttp import FormData, Payload
11
12
from ...exceptions import CommandReturnIsEmpty
0 ignored issues
show
introduced by
Cannot import 'exceptions' due to syntax error 'EOF while scanning triple-quoted string literal (<unknown>, line 293)'
Loading history...
13
from ..app.interaction_base import CallbackType
0 ignored issues
show
Unused Code introduced by
Unused CallbackType imported from app.interaction_base
Loading history...
14
15
if TYPE_CHECKING:
16
    from typing import Dict, Union, List, Optional, Tuple
0 ignored issues
show
introduced by
Imports from package typing are not grouped
Loading history...
17
18
19
    from .embed import Embed
20
    from ..user.user import User
21
    from ..guild.role import Role
22
    from .component import MessageComponent
23
    from ...utils.snowflake import Snowflake
24
    from .user_message import AllowedMentionTypes
25
    from ..app.interactions import InteractionFlags
0 ignored issues
show
Bug introduced by
The name InteractionFlags does not seem to exist in module pincer.objects.app.interactions.
Loading history...
26
27
PILLOW_IMPORT = True
28
29
try:
30
    from PIL.Image import Image
31
except (ModuleNotFoundError, ImportError):
32
    PILLOW_IMPORT = False
33
34
if TYPE_CHECKING:
35
    from ..message.embed import Embed
36
    from ..message.file import File
37
    from ..message.user_message import AllowedMentions
38
    from ..app import InteractionFlags
39
    from .component import MessageComponent
40
41
42 View Code Duplication
@dataclass
0 ignored issues
show
Bug introduced by
class already defined line 37
Loading history...
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
43
class AllowedMentions(APIObject):
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'APIObject'
Loading history...
44
    """Represents the entities the client can mention
45
46
    Attributes
47
    ----------
48
    parse: List[:class:`~pincer.objects.message.user_message.AllowedMentionTypes`]
49
        An array of allowed mention types to parse from the content.
50
    roles: List[Union[:class:`~pincer.objects.guild.role.Role`, :class:`~pincer.utils.snowflake.Snowflake`]]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (108/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
51
        List of ``Role`` objects or snowflakes of allowed mentions.
52
    users: List[Union[:class:`~pincer.objects.user.user.User` :class:`~pincer.utils.snowflake.Snowflake`]]
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (106/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
53
        List of ``user`` objects or snowflakes of allowed mentions.
54
    reply: :class:`bool`
55
        If replies should mention the author.
56
        |default| :data:`True`
57
    """  # noqa: E501
58
59
    parse: List[AllowedMentionTypes]
60
    roles: List[Union[Role, Snowflake]]
61
    users: List[Union[User, Snowflake]]
62
    reply: bool = True
63
64
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
65
        def get_str_id(obj: Union[Snowflake, User, Role]) -> str:
66
            if hasattr(obj, "id"):
67
                obj = obj.id
68
69
            return str(obj)
70
71
        return {
72
            "parse": self.parse,
73
            "roles": list(map(get_str_id, self.roles)),
74
            "users": list(map(get_str_id, self.users)),
75
            "replied_user": self.reply
76
        }
77
78
79
@dataclass
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
80
class Message:
81
    """A discord message that will be send to discord
82
83
    Attributes
84
    ----------
85
    content: :class:`str`
86
        The text in the message.
87
        |default| ``""``
88
    attachments: Optional[List[:class:`~pincer.objects.message.file.File`]]
89
        Attachments on the message. This is a File object. You can also attach
90
        a Pillow Image or string. Pillow images will be converted to PNGs. They
91
        will use the naming sceme ``image%`` where % is the images index in the
92
        attachments array. Strings will be read as a filepath. The name of the
93
        file that the string points to will be used as the name.
94
    tts: Optional[:class:`bool`]
95
        Whether the message should be spoken to the user.
96
        |default| :data:`False`
97
    embeds: Optional[List[:class:`~pincer.objects.message.embed.Embed`]]
98
        Embed attached to the message. This is an Embed object.
99
    allowed_mentions: Optional[:class:`~pincer.objects.message.message.AllowedMentions`]
100
        The allowed mentions for the message.
101
    components: Optional[List[:class:`~pincer.objects.message.component.MessageComponent`]]
102
        The components of the message.
103
    flags: Optional[:class:`~pincer.objects.app.interactions.InteractionFlags`]
104
        The interaction flags for the message.
105
    type: Optional[:class:`~pincer.objects.app.interaction_base.CallbackType`]
106
        The type of the callback.
107
    """  # noqa: E501
108
109
    content: str = ''
110
    attachments: Optional[List[File]] = None
111
    tts: Optional[bool] = False
112
    embeds: Optional[List[Embed]] = None
113
    allowed_mentions: Optional[AllowedMentions] = None
114
    components: Optional[List[MessageComponent]] = None
115
    flags: Optional[InteractionFlags] = None
116
    delete_after: Optional[float] = None
117
118
    def __post_init__(self):
119
        if self.delete_after and self.delete_after < 0:
120
            raise ValueError(
121
                "Message can not be deleted after a negative amount of "
122
                "seconds!"
123
            )
124
125
        if not self.attachments:
126
            return
127
128
        attch = []
129
130
        for count, value in enumerate(self.attachments):
131
            if isinstance(value, File):
132
                attch.append(value)
133
            elif PILLOW_IMPORT and isinstance(value, Image):
134
                attch.append(File.from_pillow_image(
135
                    value,
136
                    f"image{count}.png",
137
                ))
138
            elif isinstance(value, str):
139
                attch.append(File.from_file(value))
140
            else:
141
                raise ValueError(f"Attachment {count} is invalid type.")
142
143
        self.attachments = attch
144
145
    @property
146
    def isempty(self) -> bool:
147
        """:class:`bool`: If the message is empty."""
148
149
        return (
150
            len(self.content) < 1
151
            and not self.embeds
152
            and not self.attachments
153
        )
154
155
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
156
157
        allowed_mentions = (
158
            self.allowed_mentions.to_dict()
159
            if self.allowed_mentions else {}
160
        )
161
162
        # Attachments aren't serialized
163
        # because they are not sent as part of the json
164
        resp = {
165
            "content": self.content,
166
            "tts": self.tts,
167
            "flags": self.flags,
168
            "embeds": [embed.to_dict() for embed in (self.embeds or [])],
169
            "allowed_mentions": allowed_mentions,
170
            "components": [
171
                components.to_dict() for components in (self.components or [])
172
            ]
173
        }
174
175
        # IDE does not recognise return type of filter properly.
176
        # noinspection PyTypeChecker
177
        return dict(filter(
178
            lambda kv: kv[1],
179
            resp.items()
180
        ))
181
182
    def serialize(self) -> Tuple[str, Union[Payload, Dict]]:
183
        """
184
        Creates the data that the discord API wants for the message object
185
186
        :return: (content_type, data)
187
188
        :raises CommandReturnIsEmpty:
189
            Command does not have content, an embed, or attachment.
190
        """
191
        if self.isempty:
192
            raise CommandReturnIsEmpty("Cannot return empty message.")
193
194
        if not self.attachments:
195
            return "application/json", self.to_dict()
196
197
        form = FormData()
198
        form.add_field("payload_json", dumps(self.to_dict()))
199
200
        for file in self.attachments:
201
            form.add_field("file", file.content, filename=file.filename)
202
203
        payload = form()
204
        return payload.headers["Content-Type"], payload
205