from typing import Literal, Optional, Union from hatchway import Field, Schema from activities import models as activities_models from core.html import FediverseHtmlParser from users import models as users_models from users.services import IdentityService class Application(Schema): id: str name: str website: str | None client_id: str client_secret: str redirect_uri: str = Field(alias="redirect_uris") class CustomEmoji(Schema): shortcode: str url: str static_url: str visible_in_picker: bool category: str @classmethod def from_emoji(cls, emoji: activities_models.Emoji) -> "CustomEmoji": return cls(**emoji.to_mastodon_json()) class AccountField(Schema): name: str value: str verified_at: str | None class Account(Schema): id: str username: str acct: str url: str display_name: str note: str avatar: str avatar_static: str header: str | None = Field(...) header_static: str | None = Field(...) locked: bool fields: list[AccountField] emojis: list[CustomEmoji] bot: bool group: bool discoverable: bool moved: Union[None, bool, "Account"] suspended: bool = False limited: bool = False created_at: str last_status_at: str | None = Field(...) statuses_count: int followers_count: int following_count: int source: dict | None @classmethod def from_identity( cls, identity: users_models.Identity, include_counts: bool = True, source=False, ) -> "Account": return cls( **identity.to_mastodon_json(include_counts=include_counts, source=source) ) class MediaAttachment(Schema): id: str type: Literal["unknown", "image", "gifv", "video", "audio"] url: str preview_url: str remote_url: str | None meta: dict description: str | None blurhash: str | None @classmethod def from_post_attachment( cls, attachment: activities_models.PostAttachment ) -> "MediaAttachment": return cls(**attachment.to_mastodon_json()) class PollOptions(Schema): title: str votes_count: int | None class Poll(Schema): id: str expires_at: str | None expired: bool multiple: bool votes_count: int voters_count: int | None voted: bool own_votes: list[int] options: list[PollOptions] emojis: list[CustomEmoji] @classmethod def from_post( cls, post: activities_models.Post, identity: users_models.Identity | None = None, ) -> "Poll": return cls(**post.type_data.to_mastodon_json(post, identity=identity)) class StatusMention(Schema): id: str username: str url: str acct: str class StatusTag(Schema): name: str url: str class Status(Schema): id: str uri: str created_at: str account: Account content: str visibility: Literal["public", "unlisted", "private", "direct"] sensitive: bool spoiler_text: str media_attachments: list[MediaAttachment] mentions: list[StatusMention] tags: list[StatusTag] emojis: list[CustomEmoji] reblogs_count: int favourites_count: int replies_count: int url: str | None = Field(...) in_reply_to_id: str | None = Field(...) in_reply_to_account_id: str | None = Field(...) reblog: Optional["Status"] = Field(...) poll: Poll | None = Field(...) card: None = Field(...) language: None = Field(...) text: str | None = Field(...) edited_at: str | None favourited: bool = False reblogged: bool = False muted: bool = False bookmarked: bool = False pinned: bool = False @classmethod def from_post( cls, post: activities_models.Post, interactions: dict[str, set[str]] | None = None, bookmarks: set[str] | None = None, identity: users_models.Identity | None = None, ) -> "Status": return cls( **post.to_mastodon_json( interactions=interactions, bookmarks=bookmarks, identity=identity ) ) @classmethod def map_from_post( cls, posts: list[activities_models.Post], identity: users_models.Identity, ) -> list["Status"]: interactions = activities_models.PostInteraction.get_post_interactions( posts, identity ) bookmarks = users_models.Bookmark.for_identity(identity, posts) return [ cls.from_post( post, interactions=interactions, bookmarks=bookmarks, identity=identity ) for post in posts ] @classmethod def from_timeline_event( cls, timeline_event: activities_models.TimelineEvent, interactions: dict[str, set[str]] | None = None, bookmarks: set[str] | None = None, identity: users_models.Identity | None = None, ) -> "Status": return cls( **timeline_event.to_mastodon_status_json( interactions=interactions, bookmarks=bookmarks, identity=identity ) ) @classmethod def map_from_timeline_event( cls, events: list[activities_models.TimelineEvent], identity: users_models.Identity, ) -> list["Status"]: interactions = activities_models.PostInteraction.get_event_interactions( events, identity ) bookmarks = users_models.Bookmark.for_identity( identity, events, "subject_post_id" ) return [ cls.from_timeline_event( event, interactions=interactions, bookmarks=bookmarks, identity=identity ) for event in events ] class StatusSource(Schema): id: str text: str spoiler_text: str @classmethod def from_post(cls, post: activities_models.Post): return cls( id=post.id, text=FediverseHtmlParser(post.content).plain_text, spoiler_text=post.summary or "", ) class Conversation(Schema): id: str unread: bool accounts: list[Account] last_status: Status | None = Field(...) class Notification(Schema): id: str type: Literal[ "mention", "status", "reblog", "follow", "follow_request", "favourite", "poll", "update", "admin.sign_up", "admin.report", ] created_at: str account: Account status: Status | None @classmethod def from_timeline_event( cls, event: activities_models.TimelineEvent, ) -> "Notification": return cls(**event.to_mastodon_notification_json()) class Tag(Schema): name: str url: str history: dict followed: bool | None @classmethod def from_hashtag( cls, hashtag: activities_models.Hashtag, followed: bool | None = None, ) -> "Tag": return cls(**hashtag.to_mastodon_json(followed=followed)) class FollowedTag(Tag): id: str @classmethod def from_follow( cls, follow: users_models.HashtagFollow, ) -> "FollowedTag": return cls(id=follow.id, **follow.hashtag.to_mastodon_json(followed=True)) @classmethod def map_from_follows( cls, hashtag_follows: list[users_models.HashtagFollow], ) -> list["Tag"]: return [cls.from_follow(follow) for follow in hashtag_follows] class FeaturedTag(Schema): id: str name: str url: str statuses_count: int last_status_at: str class Search(Schema): accounts: list[Account] statuses: list[Status] hashtags: list[Tag] class Relationship(Schema): id: str following: bool followed_by: bool showing_reblogs: bool notifying: bool blocking: bool blocked_by: bool muting: bool muting_notifications: bool requested: bool domain_blocking: bool endorsed: bool note: str @classmethod def from_identity_pair( cls, identity: users_models.Identity, from_identity: users_models.Identity, ) -> "Relationship": return cls( **IdentityService(identity).mastodon_json_relationship(from_identity) ) class Context(Schema): ancestors: list[Status] descendants: list[Status] class FamiliarFollowers(Schema): id: str accounts: list[Account] class Announcement(Schema): id: str content: str starts_at: str | None = Field(...) ends_at: str | None = Field(...) all_day: bool published_at: str updated_at: str read: bool | None # Only missing for anonymous responses mentions: list[Account] statuses: list[Status] tags: list[Tag] emojis: list[CustomEmoji] reactions: list @classmethod def from_announcement( cls, announcement: users_models.Announcement, user: users_models.User, ) -> "Announcement": return cls(**announcement.to_mastodon_json(user=user)) class List(Schema): id: str title: str replies_policy: Literal[ "followed", "list", "none", ] class Preferences(Schema): posting_default_visibility: Literal[ "public", "unlisted", "private", "direct", ] = Field(alias="posting:default:visibility") posting_default_sensitive: bool = Field(alias="posting:default:sensitive") posting_default_language: str | None = Field(alias="posting:default:language") reading_expand_media: Literal[ "default", "show_all", "hide_all", ] = Field(alias="reading:expand:media") reading_expand_spoilers: bool = Field(alias="reading:expand:spoilers") @classmethod def from_identity( cls, identity: users_models.Identity, ) -> "Preferences": visibility_mapping = { activities_models.Post.Visibilities.public: "public", activities_models.Post.Visibilities.unlisted: "unlisted", activities_models.Post.Visibilities.followers: "private", activities_models.Post.Visibilities.mentioned: "direct", activities_models.Post.Visibilities.local_only: "public", } return cls.parse_obj( { "posting:default:visibility": visibility_mapping[ identity.config_identity.default_post_visibility ], "posting:default:sensitive": False, "posting:default:language": None, "reading:expand:media": "default", "reading:expand:spoilers": identity.config_identity.expand_linked_cws, } )