federation/federation/entities/base.py

186 wiersze
5.8 KiB
Python

from typing import Dict, Tuple
from mimetypes import guess_type
from dirty_validators.basic import Email
from federation.entities.activitypub.enums import ActivityType
from federation.entities.mixins import (
PublicMixin, TargetIDMixin, ParticipationMixin, CreatedAtMixin, RawContentMixin, OptionalRawContentMixin,
EntityTypeMixin, ProviderDisplayNameMixin, RootTargetIDMixin, BaseEntity)
from federation.utils.network import fetch_content_type
class Accept(CreatedAtMixin, TargetIDMixin, BaseEntity):
"""An acceptance message for some target."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ID not required for accept
self._required.remove('id')
class Image(OptionalRawContentMixin, CreatedAtMixin, BaseEntity):
"""Reflects a single image, possibly linked to another object."""
url: str = ""
name: str = ""
height: int = 0
width: int = 0
inline: bool = False
media_type: str = ""
_default_activity = ActivityType.CREATE
_valid_media_types: Tuple[str] = (
"image/jpeg",
"image/png",
"image/gif",
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._required += ["url"]
self._required.remove("id")
self._required.remove("actor_id")
if self.url and not self.media_type:
self.media_type = self.get_media_type()
def get_media_type(self) -> str:
media_type = guess_type(self.url)[0] or fetch_content_type(self.url)
if media_type in self._valid_media_types:
return media_type
return ""
class Audio(OptionalRawContentMixin, CreatedAtMixin, BaseEntity):
pass
class Video(OptionalRawContentMixin, CreatedAtMixin, BaseEntity):
pass
class Comment(RawContentMixin, ParticipationMixin, CreatedAtMixin, RootTargetIDMixin, BaseEntity):
"""Represents a comment, linked to another object."""
participation = "comment"
url = ""
_allowed_children = (Image,)
_default_activity = ActivityType.CREATE
class Follow(CreatedAtMixin, TargetIDMixin, BaseEntity):
"""Represents a handle following or unfollowing another handle."""
following = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._required += ["following"]
# ID not required for follow
self._required.remove('id')
class Post(RawContentMixin, PublicMixin, CreatedAtMixin, ProviderDisplayNameMixin, BaseEntity):
"""Reflects a post, status message, etc, which will be composed from the message or to the message."""
location = ""
url = ""
_allowed_children = (Image,)
_default_activity = ActivityType.CREATE
class Reaction(ParticipationMixin, CreatedAtMixin, BaseEntity):
"""Represents a reaction to another object, for example a like."""
participation = "reaction"
reaction = ""
_default_activity = ActivityType.CREATE
_reaction_valid_values = ["like"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._required += ["reaction"]
def validate_reaction(self):
"""Ensure reaction is of a certain type.
Mainly for future expansion.
"""
if self.reaction not in self._reaction_valid_values:
raise ValueError("reaction should be one of: {valid}".format(
valid=", ".join(self._reaction_valid_values)
))
class Relationship(CreatedAtMixin, TargetIDMixin, BaseEntity):
"""Represents a relationship between two handles."""
relationship = ""
_relationship_valid_values = ["sharing", "following", "ignoring", "blocking"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._required += ["relationship"]
def validate_relationship(self):
"""Ensure relationship is of a certain type."""
if self.relationship not in self._relationship_valid_values:
raise ValueError("relationship should be one of: {valid}".format(
valid=", ".join(self._relationship_valid_values)
))
class Profile(CreatedAtMixin, OptionalRawContentMixin, PublicMixin, BaseEntity):
"""Represents a profile for a user."""
atom_url = ""
email = ""
gender = ""
image_urls = None
location = ""
name = ""
nsfw = False
public_key = ""
tag_list = None
url = ""
username = ""
inboxes: Dict = None
_allowed_children = (Image,)
def __init__(self, *args, **kwargs):
self.image_urls = {
"small": "", "medium": "", "large": ""
}
self.inboxes = {
"private": None,
"public": None,
}
self.tag_list = []
super().__init__(*args, **kwargs)
# As an exception, a Profile does not require to have an `actor_id`
self._required.remove('actor_id')
def validate_email(self):
if self.email:
validator = Email()
if not validator.is_valid(self.email):
raise ValueError("Email is not valid")
class Retraction(CreatedAtMixin, TargetIDMixin, EntityTypeMixin, BaseEntity):
"""Represents a retraction of content by author."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ID not required for retraction
self._required.remove('id')
class Share(CreatedAtMixin, TargetIDMixin, EntityTypeMixin, OptionalRawContentMixin, PublicMixin,
ProviderDisplayNameMixin, BaseEntity):
"""Represents a share of another entity.
``entity_type`` defaults to "Post" but can be any base entity class name. It should be the class name of the
entity that was shared.
The optional ``raw_content`` can be used for a "quoted share" case where the sharer adds their own note to the
share.
"""
entity_type = "Post"