Completed
Push — main ( f3584d...05f81d )
by Yohann
15s queued 11s
created

Message.__post_init__()   B

Complexity

Conditions 6

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 21
rs 8.6666
c 0
b 0
f 0
cc 6
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 Dict, Tuple, Union, List, Optional, TYPE_CHECKING
8
9
from aiohttp import FormData, Payload
10
import json
0 ignored issues
show
introduced by
standard import "import json" should be placed before "from aiohttp import FormData, Payload"
Loading history...
11
from PIL.Image import Image
12
13
from ..app.interaction_base import CallbackType
14
from ..guild.role import Role
15
from ..message.file import File
16
from ..message.embed import Embed
17
from ..message.user_message import AllowedMentionTypes
18
from ..user import User
19
from ...exceptions import CommandReturnIsEmpty
20
from ...utils.api_object import APIObject
21
from ...utils.snowflake import Snowflake
22
23
if TYPE_CHECKING:
24
    from ..app import InteractionFlags
25
    from .component import MessageComponent
26
27
28
@dataclass
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
29
class AllowedMentions(APIObject):
30
    parse: List[AllowedMentionTypes]
31
    roles: List[Union[Role, Snowflake]]
32
    users: List[Union[User, Snowflake]]
33
    reply: bool = True
34
35
    def to_dict(self):
36
        def get_str_id(obj: Union[Snowflake, User, Role]) -> str:
37
            if hasattr(obj, "id"):
38
                obj = obj.id
39
40
            return str(obj)
41
42
        return {
43
            "parse": self.parse,
44
            "roles": list(map(get_str_id, self.roles)),
45
            "users": list(map(get_str_id, self.users)),
46
            "replied_user": self.reply
47
        }
48
49
50
@dataclass
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
51
class Message:
52
    # TODO: Docs for tts, allowed_mentions, components, flags, and type.
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
53
54
    """
55
    A discord message that will be send to discord
56
57
    :param content:
58
        The text in the message.
59
60
    :param attachments:
61
        Attachments on the message. This is a File object. You can also attach
62
        a Pillow Image or string. Pillow images will be converted to PNGs. They
63
        will use the naming sceme ``image%`` where % is the images index in the
64
        attachments array. Strings will be read as a filepath. The name of the
65
        file that the string points to will be used as the name.
66
67
    :param embeds:
68
        Embed attached to the message. This is an Embed object.
69
    """
70
71
    content: str = ''
72
    attachments: Optional[List[File]] = None
73
    tts: Optional[bool] = False
74
    embeds: Optional[List[Embed]] = None
75
    allowed_mentions: Optional[AllowedMentions] = None
76
    components: Optional[List[MessageComponent]] = None
77
    flags: Optional[InteractionFlags] = None
78
    type: Optional[CallbackType] = None
79
80
    def __post_init__(self):
81
82
        if not self.attachments:
83
            return
84
85
        attch = []
86
87
        for count, value in enumerate(self.attachments):
88
            if isinstance(value, File):
89
                attch.append(value)
90
            elif isinstance(value, Image):
91
                attch.append(File.from_pillow_image(
92
                    value,
93
                    f"image{count}.png",
94
                ))
95
            elif isinstance(value, str):
96
                attch.append(File.from_file(value))
97
            else:
98
                raise ValueError(f"Attachment {count} is invalid type.")
99
100
        self.attachments = attch
101
102
    @property
103
    def isempty(self) -> bool:
104
        """
105
        :return:
106
            Returns true if a message is empty.
107
        """
108
109
        return (
110
            len(self.content) < 1
111
            and not self.embeds
112
            and not self.attachments
113
        )
114
115
    def to_dict(self):
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
116
117
        allowed_mentions = (
118
            self.allowed_mentions.to_dict()
119
            if self.allowed_mentions else {}
120
        )
121
122
        # Attachments aren't serialized
123
        # because they are not sent as part of the json
124
        resp = {
125
            "content": self.content,
126
            "tts": self.tts,
127
            "flags": self.flags,
128
            "embeds": [embed.to_dict() for embed in (self.embeds or [])],
129
            "allowed_mentions": allowed_mentions,
130
            "components": [
131
                components.to_dict() for components in (self.components or [])
132
            ]
133
        }
134
135
        return {
136
            "type": self.type or CallbackType.MESSAGE,
137
            "data": {k: i for k, i in resp.items() if i}
138
        }
139
140
    def serialize(self) -> Tuple[str, Union[Payload, Dict]]:
141
        """
142
        Creates the data that the discord API wants for the message object
143
144
        :return: (content_type, data)
145
146
        :raises CommandReturnIsEmpty:
147
            Command does not have content, an embed, or attachment.
148
        """
149
150
        if self.isempty:
151
            raise CommandReturnIsEmpty("Cannot return empty message.")
152
153
        if not self.attachments:
154
            return "application/json", self.to_dict()
155
156
        form = FormData()
157
        form.add_field("payload_json", json.dumps(self.to_dict()))
158
159
        for file in self.attachments:
160
            form.add_field("file", file.content, filename=file.filename)
161
162
        payload = form()
163
        return payload.headers["Content-Type"], payload
164