Passed
Push — main ( f4e301...b6de8c )
by Yohann
02:53
created

pincer.objects.message.embed.Embed.add_fields()   A

Complexity

Conditions 5

Size

Total Lines 52
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 52
rs 9.0333
c 0
b 0
f 0
cc 5
nop 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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, field
7
from datetime import datetime
8
from re import match
9
from typing import TYPE_CHECKING
10
11
from ...exceptions import InvalidUrlError, EmbedFieldError
12
from ...utils.api_object import APIObject
0 ignored issues
show
Bug introduced by
The name api_object does not seem to exist in module pincer.utils.
Loading history...
introduced by
Cannot import 'utils.api_object' due to syntax error 'invalid syntax (<unknown>, line 87)'
Loading history...
13
from ...utils.types import MISSING
14
15
if TYPE_CHECKING:
16
    from typing import Any, Callable, Dict, Iterable, Union, Optional
17
18
    from ...utils.types import APINullable
19
20
21
def _field_size(_field: str) -> int:
22
    """
23
    The Discord API removes white space
24
        when counting the length of a field.
25
26
    :param _field:
27
        The field.
28
29
    :return:
30
        Length of the string without white space.
31
    """
32
    return 0 if _field == MISSING else len(_field.strip())
33
34
35
def _is_valid_url(url: str) -> bool:
36
    """
37
    Checks whether the url is a proper and valid url.
38
    (matches for http and attachment protocol.
39
40
    :param url:
41
        The url which must be checked.
42
43
    :return:
44
        Whether the provided url is valid.
45
    """
46
    stmt = (
47
        r"(http[s]|attachment)"
48
        r"?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|"
49
        r"(?:%[0-9a-fA-F][0-9a-fA-F]))+"
50
    )
51
52
    return bool(match(stmt, url))
53
54
55
def _check_if_valid_url(url: str):
56
    """Checks if the provided url is valid.
57
58
    Raises
59
    ------
60
    :class:`~pincer.exceptions.InvalidUrlError`
61
        if the url didn't match the url regex.
62
        (which means that it was malformed or didn't match the http/attachment
63
        protocol).
64
    """
65
    if not _is_valid_url(url):
66
        raise InvalidUrlError(
67
            "Url was malformed or wasn't of protocol http(s)/attachment."
68
        )
69
70
71
@dataclass
72
class EmbedAuthor:
73
    """Representation of the Embed Author class
74
75
    Attributes
76
    ----------
77
    name: APINullable[:class:`str`]
78
        Name of the author
79
    url: APINullable[:class:`str`]
80
        Url of the author
81
    icon_url: APINullable[:class:`str`]
82
        Url of the author icon
83
    proxy_icon_url: APINullable[:class:`str`]
84
        A proxied url of the author icon
85
    """
86
87
    icon_url: APINullable[str] = MISSING
88
    name: APINullable[str] = MISSING
89
    proxy_icon_url: APINullable[str] = MISSING
90
    url: APINullable[str] = MISSING
91
92
    def __post_init__(self):  # stop documenting special methods
93
        if _field_size(self.name) > 256:
94
            raise EmbedFieldError.from_desc("Author name", 256, len(self.name))
95
96
        _check_if_valid_url(self.url)
97
98
99
@dataclass
100
class EmbedImage:
101
    """Representation of the Embed Image class
102
103
    Attributes
104
    ----------
105
    url: APINullable[:class:`str`]
106
        Source url of the image
107
    proxy_url: APINullable[:class:`str`]
108
        A proxied url of the image
109
    height: APINullable[:class:`int`]
110
        Height of the image
111
    width: APINullable[:class:`int`]
112
        Width of the image
113
    """
114
115
    url: APINullable[str] = MISSING
116
    proxy_url: APINullable[str] = MISSING
117
    height: APINullable[int] = MISSING
118
    width: APINullable[int] = MISSING
119
120
    def __post_init__(self):
121
        _check_if_valid_url(self.url)
122
123
124
@dataclass
125
class EmbedProvider:
126
    """Representation of the Provider class
127
128
    Attributes
129
    ----------
130
    name: APINullable[:class:`str`]
131
        Name of the provider
132
    url: APINullable[:class:`str`]
133
        Url of the provider
134
    """
135
136
    name: APINullable[str] = MISSING
137
    url: APINullable[str] = MISSING
138
139
140
@dataclass
141
class EmbedThumbnail:
142
    """Representation of the Embed Thumbnail class
143
144
    Attributes
145
    ----------
146
    url: APINullable[:class:`str`]
147
        Source url of the thumbnail
148
    proxy_url: APINullable[:class:`str`]
149
        A proxied url of the thumbnail
150
    height: APINullable[:class:`int`]
151
        Height of the thumbnail
152
    width: APINullable[:class:`int`]
153
        Width of the thumbnail
154
    """
155
156
    url: APINullable[str] = MISSING
157
    proxy_url: APINullable[str] = MISSING
158
    height: APINullable[int] = MISSING
159
    width: APINullable[int] = MISSING
160
161
    def __post_init__(self):
162
        _check_if_valid_url(self.url)
163
164
165
@dataclass
166
class EmbedVideo:
167
    """Representation of the Embed Video class
168
169
    Attributes
170
    ----------
171
    url: APINullable[:class:`str`]
172
        Source url of the video
173
    proxy_url: APINullable[:class:`str`]
174
        A proxied url of the video
175
    height: APINullable[:class:`int`]
176
        Height of the video
177
    width: APINullable[:class:`int`]
178
        Width of the video
179
    """
180
181
    height: APINullable[int] = MISSING
182
    url: APINullable[str] = MISSING
183
    proxy_url: APINullable[str] = MISSING
184
    width: APINullable[int] = MISSING
185
186
187
@dataclass
188
class EmbedFooter:
189
    """Representation of the Embed Footer class
190
191
    Attributes
192
    ----------
193
    text: :class:`str`
194
        Footer text
195
    icon_url: APINullable[:class:`str`]
196
        Url of the footer icon
197
    proxy_icon_url: APINullable[:class:`str`]
198
        A proxied url of the footer icon
199
200
    Raises
201
    ------
202
    EmbedFieldError:
203
        Text is longer than 2048 characters
204
    """
205
206
    text: str
207
208
    icon_url: APINullable[str] = MISSING
209
    proxy_icon_url: APINullable[str] = MISSING
210
211
    def __post_init__(self):
212
        if _field_size(self.text) > 2048:
213
            raise EmbedFieldError.from_desc("Footer text", 2048, len(self.text))
214
215
216
@dataclass
217
class EmbedField:
218
    """Representation of the Embed Field class
219
220
    Attributes
221
    ----------
222
    name: :class:`str`
223
        The name of the field
224
    value: :class:`str`
225
        The text in the field
226
    inline: APINullable[:class:`bool`]
227
        Whether or not this field should display inline
228
229
    Raises
230
    ------
231
    EmbedFieldError:
232
        Name is longer than 256 characters
233
    EmbedFieldError:
234
        Description is longer than 1024 characters
235
    """
236
237
    name: str
238
    value: str
239
240
    inline: APINullable[bool] = MISSING
241
242
    def __post_init__(self):
243
        if _field_size(self.name) > 256:
244
            raise EmbedFieldError.from_desc("Field name", 256, len(self.name))
245
246
        if _field_size(self.value) > 1024:
247
            raise EmbedFieldError.from_desc(
248
                "Field value", 1024, len(self.value)
249
            )
250
251
252
# TODO: Handle Bad Request if embed that is too big is sent
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
253
# https://discord.com/developers/docs/resources/channel#embed-limits
254
# Currently ignored since I don't think it would make sense to put
255
# This with the Embed class
256
@dataclass
0 ignored issues
show
best-practice introduced by
Too many instance attributes (13/7)
Loading history...
257
class Embed(APIObject):
258
    """Representation of the discord Embed class
259
260
    Attributes
261
    ----------
262
    title: APINullable[:class:`str`]
263
        Embed title.
264
    description: APINullable[:class:`str`]
265
        Embed description.
266
    color: APINullable[:class:`int`]
267
        Embed color code.
268
    fields: List[:class:`~pincer.objects.message.embed.EmbedField`]
269
        Fields information.
270
    footer: APINullable[:class:`~pincer.objects.message.embed.EmbedFooter`]
271
        Footer information.
272
    image: APINullable[:class:`~pincer.objects.message.embed.EmbedImage`]
273
        Image information.
274
    provider: APINullable[:class:`~pincer.objects.message.embed.EmbedProvider`]
275
        Provider information.
276
    thumbnail: APINullable[:class:`~pincer.objects.message.embed.EmbedThumbnail`]
277
        Thumbnail information.
278
    timestamp: APINullable[:class:`str`]
279
        Timestamp of embed content in ISO format.
280
    url: APINullable[:class:`str`]
281
        Embed url.
282
    video: APINullable[:class:`~pincer.objects.message.embed.EmbedVideo`]
283
        Video information.
284
    type: APINullable[:class:`int`]
285
        type of message
286
    """
287
288
    # noqa: E501
289
290
    title: APINullable[str] = MISSING
291
    description: APINullable[str] = MISSING
292
    color: APINullable[int] = MISSING
293
    fields: list[EmbedField] = field(default_factory=list)
0 ignored issues
show
introduced by
Value 'list' is unsubscriptable
Loading history...
294
    footer: APINullable[EmbedFooter] = MISSING
295
    image: APINullable[EmbedImage] = MISSING
296
    provider: APINullable[EmbedProvider] = MISSING
297
    thumbnail: APINullable[EmbedThumbnail] = MISSING
298
    timestamp: APINullable[str] = MISSING
299
    author: APINullable[EmbedAuthor] = MISSING
300
    url: APINullable[str] = MISSING
301
    video: APINullable[EmbedVideo] = MISSING
302
    type: APINullable[int] = MISSING
303
304
    def __post_init__(self):
305
        if _field_size(self.title) > 256:
306
            raise EmbedFieldError.from_desc("Embed title", 256, len(self.title))
307
308
        if _field_size(self.description) > 4096:
309
            raise EmbedFieldError.from_desc(
310
                "Embed description", 4096, len(self.description)
311
            )
312
313
        if len(self.fields) > 25:
314
            raise EmbedFieldError.from_desc("Embed field", 25, len(self.fields))
315
316
    def set_timestamp(self, time: datetime) -> Embed:
317
        """Discord uses iso format for time stamps.
318
        This function will set the time to that format.
319
320
        Parameters
321
        ----------
322
        time : :class:`datetime.datetime`
323
            The datetime to set the timestamp to.
324
325
        Returns
326
        -------
327
        :class:`~pincer.objects.message.embed.Embed`
328
            The new embed object.
329
        """
330
        self.timestamp = time.isoformat()
331
332
        return self
333
334
    def set_author(
335
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
336
        icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
337
        name: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
338
        proxy_icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
339
        url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
340
    ) -> Embed:
341
        """Set the author message for the embed. This is the top
342
        field of the embed.
343
344
        Parameters
345
        ----------
346
        icon_url: APINullable[:class:`str`]
347
            The icon which will be next to the author name.
348
        name: APINullable[:class:`str`]
349
            The name for the author (so the message).
350
        proxy_icon_url: APINullable[:class:`str`]
351
            A proxied url of the author icon.
352
        url: APINullable[:class:`str`]
353
            The url for the author name, this will make the
354
            name field a link/url.
355
356
        Returns
357
        -------
358
        :class:`~pincer.objects.message.embed.Embed`
359
            The new embed object.
360
        """
361
362
        self.author = EmbedAuthor(
363
            icon_url=icon_url, name=name, proxy_icon_url=proxy_icon_url, url=url
364
        )
365
366
        return self
367
368
    def set_image(
369
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
370
        url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
371
        proxy_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
372
        height: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
373
        width: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
374
    ) -> Embed:
375
        """Sets an image for your embed.
376
377
        Parameters
378
        ----------
379
        url: APINullable[:class:`str`]
380
            Source url of the video
381
        proxy_url: APINullable[:class:`str`]
382
            A proxied url of the video
383
        height: APINullable[:class:`int`]
384
            Height of the video
385
        width: APINullable[:class:`int`]
386
            Width of the video
387
388
        Returns
389
        -------
390
        :class:`~pincer.objects.message.embed.Embed`
391
            The new embed object.
392
        """
393
        self.image = EmbedImage(
394
            height=height, url=url, proxy_url=proxy_url, width=width
395
        )
396
397
        return self
398
399
    def set_thumbnail(
400
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
401
        height: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
402
        url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
403
        proxy_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
404
        width: APINullable[int] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
405
    ) -> Embed:  # ? its normally smaller in the corner?
406
        """Sets the thumbnail of the embed.
407
        This image is bigger than the ``image`` property.
408
409
        url: APINullable[:class:`str`]
410
            Source url of the video
411
        proxy_url: APINullable[:class:`str`]
412
            A proxied url of the video
413
        height: APINullable[:class:`int`]
414
            Height of the video
415
        width: APINullable[:class:`int`]
416
            Width of the video
417
418
        Returns
419
        -------
420
        :class:`~pincer.objects.message.embed.Embed`
421
            The new embed object.
422
        """
423
        self.thumbnail = EmbedThumbnail(
424
            height=height, url=url, proxy_url=proxy_url, width=width
425
        )
426
427
        return self
428
429
    def set_footer(
430
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
431
        text: str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
432
        icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
433
        proxy_icon_url: APINullable[str] = MISSING,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
434
    ) -> Embed:
435
        """
436
        Sets the embed footer. This is at the bottom of your embed.
437
438
        Parameters
439
        ----------
440
        text: :class:`str`
441
            Footer text
442
        icon_url: APINullable[:class:`str`]
443
            Url of the footer icon
444
        proxy_icon_url: APINullable[:class:`str`]
445
            A proxied url of the footer icon
446
447
        Returns
448
        -------
449
        :class:`~pincer.objects.message.embed.Embed`
450
            The new embed object.
451
        """
452
        self.footer = EmbedFooter(
453
            text=text, icon_url=icon_url, proxy_icon_url=proxy_icon_url
454
        )
455
456
        return self
457
458
    def add_field(
459
        self, name: str, value: str, inline: APINullable[bool] = MISSING
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
460
    ) -> Embed:
461
        """Adds a field to the embed.
462
        An embed can contain up to 25 fields.
463
464
        Parameters
465
        ----------
466
        name: :class:`str`
467
            The name of the field
468
        value: :class:`str`
469
            The text in the field
470
        inline: APINullable[:class:`bool`]
471
            Whether or not this field should display inline
472
473
        Raises
474
        ------
475
        EmbedFieldError:
476
            Raised when there are more than 25 fields in the embed
477
        """
478
        _field = EmbedField(name=name, value=value, inline=inline)
479
480
        if len(self.fields) > 25:
481
            raise EmbedFieldError.from_desc(
482
                "Embed field", 25, len(self.fields) + 1
483
            )
484
485
        self.fields += [_field]
486
487
        return self
488
489
    def add_fields(
0 ignored issues
show
best-practice introduced by
Too many arguments (6/5)
Loading history...
490
        self,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
491
        field_list: Union[Dict[Any, Any], Iterable[Iterable[Any, Any]]],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
492
        checks: Optional[Callable[[Any], Any]] = bool,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
493
        map_title: Optional[Callable[[Any], str]] = str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
494
        map_values: Optional[Callable[[Any], str]] = str,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
495
        inline: bool = True,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
496
    ) -> Embed:
497
        """Add multiple fields from a list,
498
        dict or generator of fields with possible mapping.
499
500
        Parameters
501
        ----------
502
        field_list: Union[Dict[Any, Any], Iterable[Iterable[Any, Any]]]
503
            A iterable or generator of the fields to add.
504
            If the field_list type is a dictionary, will take items.
505
        checks: Optional[Callable[[Any], Any]]
506
            A filter function to remove embed fields.
507
        map_title: Optional[Callable[[Any], :class:`str`]]
508
            A transform function to change the titles.
509
        map_values: Optional[Callable[[Any], :class:`str`]]
510
            A transform function to change the values.
511
        inline: :class:`bool`
512
            Whether to create grid or each field on a new line.
513
514
        Raises
515
        ------
516
        EmbedFieldError:
517
            Raised when there are more than 25 fields in the embed
518
519
        Returns
520
        -------
521
        :class:`~pincer.objects.message.embed.Embed`
522
            The new embed object.
523
        """
524
525
        if isinstance(field_list, dict):
526
            field_list: Iterable[Iterable[Any, Any]] = field_list.items()
527
528
        for field_name, field_value in field_list:
529
            val = (
530
                map_values(field_value)
531
                if not isinstance(field_value, tuple)
532
                else map_values(*field_value)
533
            )
534
535
            if checks(val):
536
                self.add_field(
537
                    name=map_title(field_name), value=val, inline=inline
538
                )
539
540
        return self
541