diff --git a/src/bot/types/__init__.py b/src/bot/types/__init__.py new file mode 100644 index 0000000..16741c9 --- /dev/null +++ b/src/bot/types/__init__.py @@ -0,0 +1,41 @@ +from datetime import date, datetime, timezone + +from .embed import ( + Embed, + RichEmbed, + ImageEmbed, + VideoEmbed, + GifvEmbed, + ArticleEmbed, + LinkEmbed, +) + +__all__ = [ + "Embed", + "RichEmbed", + "ImageEmbed", + "VideoEmbed", + "GifvEmbed", + "ArticleEmbed", + "LinkEmbed", + "ISO8601Timestamp", +] + + +class ISO8601Timestamp: + def __init__(self, dt: date | datetime) -> None: + self.dt = dt + + def __str__(self) -> str: + return self.dt.isoformat() + + def __repr__(self) -> str: + return f"ISO8601Timestamp({repr(self.dt)})" + + @classmethod + def today(cls): + return cls(date.today()) + + @classmethod + def now(cls, tz: timezone | None): + return cls(datetime.now(tz)) diff --git a/src/bot/types/embed.py b/src/bot/types/embed.py new file mode 100644 index 0000000..77c59aa --- /dev/null +++ b/src/bot/types/embed.py @@ -0,0 +1,166 @@ +from typing import Annotated, Literal, TypedDict +from annotated_types import Len + +import discord + +from .timestamp import ISO8601Timestamp + + +class _EmbedFooterReq(TypedDict): + text: str + + +class _EmbedFooterOpt(TypedDict, total=False): + icon_url: str + proxy_icon_url: str + + +class EmbedFooter(_EmbedFooterReq, _EmbedFooterOpt): + pass + + +class _EmbedImageReq(TypedDict): + url: str + + +class _EmbedImageOpt(TypedDict, total=False): + proxy_url: str + height: int + width: int + + +class EmbedImage(_EmbedImageReq, _EmbedImageOpt): + pass + + +class _EmbedThumbnailReq(TypedDict): + url: str + + +class _EmbedThumbnailOpt(TypedDict, total=False): + proxy_url: str + height: int + width: int + + +class EmbedThumbnail(_EmbedThumbnailReq, _EmbedThumbnailOpt): + pass + + +class EmbedVideo(TypedDict, total=False): + url: str + proxy_url: str + height: int + width: int + + +class EmbedProvider(TypedDict, total=False): + name: str + url: str + + +class EmbedAuthor(TypedDict, total=False): + name: str + url: str + icon_url: str + proxy_icon_url: str + + +class _EmbedFieldReq(TypedDict): + name: str + value: str + + +class _EmbedFieldOpt(TypedDict, total=False): + inline: bool + + +class EmbedField(_EmbedFieldReq, _EmbedFieldOpt): + pass + + +class _EmbedBase(TypedDict, total=False): + title: str + description: str + url: str + timestamp: ISO8601Timestamp + color: discord.Colour | int + footer: EmbedFooter + image: EmbedImage + thumbnail: EmbedThumbnail + video: EmbedVideo + provider: EmbedProvider + author: EmbedAuthor + fields: Annotated[list[EmbedField], Len(max_length=25)] + + +class RichEmbedImpl(TypedDict): + type: Literal["rich"] + + +class RichEmbed(_EmbedBase, RichEmbedImpl): + pass + + +class ImageEmbedImpl(TypedDict): + type: Literal["image"] + + +class ImageEmbed(_EmbedBase, ImageEmbedImpl): + pass + + +class VideoEmbedImpl(TypedDict): + type: Literal["video"] + + +class VideoEmbed(_EmbedBase, VideoEmbedImpl): + pass + + +class GifvEmbedImpl(TypedDict): + type: Literal["givf"] + + +class GifvEmbed(_EmbedBase, GifvEmbedImpl): + pass + + +class ArticleEmbedImpl(TypedDict): + type: Literal["article"] + + +class ArticleEmbed(_EmbedBase, ArticleEmbedImpl): + pass + + +class LinkEmbedImpl(TypedDict): + type: Literal["link"] + + +class LinkEmbed(_EmbedBase, LinkEmbedImpl): + pass + + +class PollResultEmbedImpl(TypedDict): + type: Literal["poll_result"] + + +# TODO: There are a bunch of fields for poll results, but I *really* +# don't need these at the moment: +# https://discord.com/developers/docs/resources/message#embed-fields-by-embed-type-poll-result-embed-fields + + +class PollResultEmbed(_EmbedBase, PollResultEmbedImpl): + pass + + +Embed = ( + RichEmbed + | ImageEmbed + | VideoEmbed + | GifvEmbed + | ArticleEmbed + | LinkEmbed + | PollResultEmbed +) diff --git a/src/bot/types/timestamp.py b/src/bot/types/timestamp.py new file mode 100644 index 0000000..8b9444e --- /dev/null +++ b/src/bot/types/timestamp.py @@ -0,0 +1,20 @@ +from datetime import date, datetime, timezone + + +class ISO8601Timestamp: + def __init__(self, dt: date | datetime) -> None: + self.dt = dt + + def __str__(self) -> str: + return self.dt.isoformat() + + def __repr__(self) -> str: + return f"ISO8601Timestamp({repr(self.dt)})" + + @classmethod + def today(cls): + return cls(date.today()) + + @classmethod + def now(cls, tz: timezone | None): + return cls(datetime.now(tz))