from dataclasses import dataclass, field from datetime import datetime from typing import Optional, List, Literal @dataclass class Field: name: str value: str verified_at: Optional[datetime] = None @classmethod def from_dict(cls, data: dict) -> "Field": return cls( name=data["name"], value=data["value"], verified_at=( datetime.fromisoformat(data["verified_at"].rstrip("Z")) if data.get("verified_at") is not None else None ), ) @dataclass class Emoji: shortcode: str url: str static_url: str visible_in_picker: bool category: Optional[str] = None @classmethod def from_dict(cls, data: dict) -> "Emoji": return cls(**data) @dataclass class Account: id: str username: str acct: str url: str display_name: str note: str avatar: str avatar_static: str header: str header_static: str locked: bool emojis: List[Emoji] discoverable: bool created_at: datetime last_status_at: datetime statuses_count: int followers_count: int following_count: int moved: Optional["Account"] = None fields: Optional[List[Field]] = None bot: Optional[bool] = None @classmethod def from_dict(cls, data: dict) -> "Account": return cls( id=data["id"], username=data["username"], acct=data["acct"], url=data["url"], display_name=data["display_name"], note=data["note"], avatar=data["avatar"], avatar_static=data["avatar_static"], header=data["header"], header_static=data["header_static"], locked=data["locked"], emojis=list(map(Emoji.from_dict, data["emojis"])), discoverable=data["discoverable"], created_at=datetime.fromisoformat(data["created_at"].rstrip("Z")), last_status_at=datetime.fromisoformat(data["last_status_at"].rstrip("Z")), statuses_count=data["statuses_count"], followers_count=data["followers_count"], following_count=data["following_count"], moved=( Account.from_dict(data["moved"]) if data.get("moved") is not None else None ), fields=list(map(Field.from_dict, data.get("fields", []))), bot=bool(data.get("bot")), ) @dataclass class AttachmentMetaImage: @dataclass class Vec2F: x: float y: float @dataclass class AttachmentMetaImageDimensions: width: int height: int size: str aspect: float original: AttachmentMetaImageDimensions small: AttachmentMetaImageDimensions focus: Vec2F @classmethod def from_dict(cls, data: dict) -> "AttachmentMetaImage": return cls( **data, original=cls.AttachmentMetaImageDimensions(**data["original"]), small=cls.AttachmentMetaImageDimensions(**data["small"]), focus=cls.Vec2F(**data["focus"]) ) @dataclass class AttachmentMetaVideo: @dataclass class AttachmentMetaVideoOriginal: width: int height: int duration: float bitrate: int frame_rate: Optional[str] # XXX Gargron wtf? @dataclass class AttachmentMetaVideoSmall: width: int height: int size: str aspect: float length: str duration: float fps: int size: str width: int height: int aspect: float audio_encode: str audio_bitrate: str # XXX GARGROOOOONNNNNN!!!!!!! audio_channels: str # XXX I HATE YOU original: AttachmentMetaVideoOriginal small: AttachmentMetaVideoSmall @classmethod def from_dict(cls, data: dict) -> "AttachmentMetaVideo": return cls( **data, original=cls.AttachmentMetaVideoOriginal(**data["original"]), small=cls.AttachmentMetaVideoSmall(**data["small"]) ) @dataclass class Attachment: id: str type: Literal["unknown", "image", "gifv", "video", "audio"] url: str preview_url: str remote_url: Optional[str] = None preview_remote_url: Optional[str] = None meta: Optional[dict] = None description: Optional[str] = None blurhash: Optional[str] = None text_url: Optional[str] = None # XXX: DEPRECATED @classmethod def from_dict(cls, data: dict) -> "Attachment": return cls(**data) @dataclass class Application: name: str website: Optional[str] = None vapid_key: Optional[str] = None @classmethod def from_dict(cls, data: dict) -> "Application": return cls(**data) @dataclass class Mention: id: str username: str acct: str url: str @classmethod def from_dict(cls, data: dict) -> "Mention": return cls(**data) @dataclass class Tag: name: str url: str @classmethod def from_dict(cls, data: dict) -> "Tag": return cls(**data) @dataclass class Poll: @dataclass class PollOption: title: str votes_count: Optional[int] = None id: str expires_at: Optional[datetime] expired: bool multiple: bool votes_count: int voters_count: Optional[int] = None options: List[PollOption] = field(default_factory=list) emojis: List[Emoji] = field(default_factory=list) @classmethod def from_dict(cls, data: dict) -> "Poll": return cls( id=data["id"], expires_at=( datetime.fromisoformat(data["expires_at"].rstrip("Z")) if data.get("expires_at") is not None else None ), expired=data["expired"], multiple=data["multiple"], votes_count=data["votes_count"], voters_count=( int(data["voters_count"]) if data.get("voters_count") is not None else None ), options=[cls.PollOption(**opt) for opt in data["options"]], ) @dataclass class Status: id: str uri: str created_at: datetime account: Account content: str visibility: Literal["public", "unlisted", "private", "direct"] sensitive: bool spoiler_text: str media_attachments: List[Attachment] reblogs_count: int favourites_count: int replies_count: int application: Optional[Application] = None url: Optional[str] = None in_reply_to_id: Optional[str] = None in_reply_to_account_id: Optional[str] = None reblog: Optional["Status"] = None poll: Optional[Poll] = None card: Optional[dict] = None language: Optional[str] = None text: Optional[str] = None @classmethod def from_dict(cls, data: dict) -> "Status": return cls( id=data["id"], uri=data["uri"], created_at=datetime.fromisoformat(data["created_at"].rstrip("Z")), account=Account.from_dict(data["account"]), content=data["content"], visibility=data["visibility"], sensitive=data["sensitive"], spoiler_text=data["spoiler_text"], media_attachments=list( map(Attachment.from_dict, data["media_attachments"]) ), application=( Application.from_dict(data["application"]) if data.get("application") is not None else None ), reblogs_count=data["reblogs_count"], favourites_count=data["favourites_count"], replies_count=data["replies_count"], url=data.get("url"), in_reply_to_id=data.get("in_reply_to_id"), in_reply_to_account_id=data.get("in_reply_to_account_id"), reblog=( Status.from_dict(data["reblog"]) if data.get("reblog") is not None else None ), poll=( Poll.from_dict(data["poll"]) if data.get("poll") is not None else None ), card=data.get("card"), language=data.get("language"), text=data.get("text"), ) @property def link(self) -> str: return self.account.url + "/" + str(self.id)