diff --git a/api/schemas.py b/api/schemas.py index f96371e..087bee1 100644 --- a/api/schemas.py +++ b/api/schemas.py @@ -273,6 +273,14 @@ class Tag(Schema): return cls(**hashtag.to_mastodon_json()) +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] @@ -337,3 +345,54 @@ class Announcement(Schema): 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, + } + ) diff --git a/api/urls.py b/api/urls.py index 47f2420..0b1553b 100644 --- a/api/urls.py +++ b/api/urls.py @@ -5,14 +5,19 @@ from api.views import ( accounts, announcements, apps, + bookmarks, emoji, filters, + follow_requests, instance, + lists, media, notifications, polls, + preferences, search, statuses, + tags, timelines, trends, ) @@ -35,19 +40,27 @@ urlpatterns = [ path("v1/accounts//unmute", accounts.account_unmute), path("v1/accounts//following", accounts.account_following), path("v1/accounts//followers", accounts.account_followers), + path("v1/accounts//featured_tags", accounts.account_featured_tags), # Announcements path("v1/announcements", announcements.announcement_list), path("v1/announcements//dismiss", announcements.announcement_dismiss), # Apps path("v1/apps", apps.add_app), + # Bookmarks + path("v1/bookmarks", bookmarks.bookmarks), # Emoji path("v1/custom_emojis", emoji.emojis), # Filters path("v2/filters", filters.list_filters), path("v1/filters", filters.list_filters), + # Follow requests + path("v1/follow_requests", follow_requests.follow_requests), # Instance path("v1/instance", instance.instance_info_v1), + path("v1/instance/peers", instance.peers), path("v2/instance", instance.instance_info_v2), + # Lists + path("v1/lists", lists.get_lists), # Media path("v1/media", media.upload_media), path("v2/media", media.upload_media), @@ -66,6 +79,8 @@ urlpatterns = [ # Polls path("v1/polls/", polls.get_poll), path("v1/polls//votes", polls.vote_poll), + # Preferences + path("v1/preferences", preferences.preferences), # Search path("v2/search", search.search), # Statuses @@ -76,6 +91,8 @@ urlpatterns = [ path("v1/statuses//favourited_by", statuses.favourited_by), path("v1/statuses//reblog", statuses.reblog_status), path("v1/statuses//unreblog", statuses.unreblog_status), + # Tags + path("v1/followed_tags", tags.followed_tags), # Timelines path("v1/timelines/home", timelines.home), path("v1/timelines/public", timelines.public), diff --git a/api/views/accounts.py b/api/views/accounts.py index 371aca0..61421f4 100644 --- a/api/views/accounts.py +++ b/api/views/accounts.py @@ -349,3 +349,9 @@ def account_followers( request=request, include_params=["limit"], ) + + +@api_view.get +def account_featured_tags(request: HttpRequest, id: str) -> list[schemas.FeaturedTag]: + # Not implemented yet + return [] diff --git a/api/views/bookmarks.py b/api/views/bookmarks.py new file mode 100644 index 0000000..ca382eb --- /dev/null +++ b/api/views/bookmarks.py @@ -0,0 +1,18 @@ +from django.http import HttpRequest +from hatchway import api_view + +from api import schemas +from api.decorators import scope_required + + +@scope_required("read:bookmarks") +@api_view.get +def bookmarks( + request: HttpRequest, + max_id: str | None = None, + since_id: str | None = None, + min_id: str | None = None, + limit: int = 20, +) -> list[schemas.Status]: + # We don't implement this yet + return [] diff --git a/api/views/follow_requests.py b/api/views/follow_requests.py new file mode 100644 index 0000000..e188ba5 --- /dev/null +++ b/api/views/follow_requests.py @@ -0,0 +1,18 @@ +from django.http import HttpRequest +from hatchway import api_view + +from api import schemas +from api.decorators import scope_required + + +@scope_required("read:follows") +@api_view.get +def follow_requests( + request: HttpRequest, + max_id: str | None = None, + since_id: str | None = None, + min_id: str | None = None, + limit: int = 40, +) -> list[schemas.Account]: + # We don't implement this yet + return [] diff --git a/api/views/instance.py b/api/views/instance.py index f539fbd..165c12f 100644 --- a/api/views/instance.py +++ b/api/views/instance.py @@ -130,3 +130,12 @@ def instance_info_v2(request) -> dict: }, "rules": [], } + + +@api_view.get +def peers(request) -> list[str]: + return list( + Domain.objects.filter(local=False, blocked=False).values_list( + "domain", flat=True + ) + ) diff --git a/api/views/lists.py b/api/views/lists.py new file mode 100644 index 0000000..2ff10d2 --- /dev/null +++ b/api/views/lists.py @@ -0,0 +1,12 @@ +from django.http import HttpRequest +from hatchway import api_view + +from api import schemas +from api.decorators import scope_required + + +@scope_required("read:lists") +@api_view.get +def get_lists(request: HttpRequest) -> list[schemas.List]: + # We don't implement this yet + return [] diff --git a/api/views/preferences.py b/api/views/preferences.py new file mode 100644 index 0000000..12f27b7 --- /dev/null +++ b/api/views/preferences.py @@ -0,0 +1,13 @@ +from django.http import HttpRequest +from hatchway import api_view + +from api import schemas +from api.decorators import scope_required + + +@scope_required("read:accounts") +@api_view.get +def preferences(request: HttpRequest) -> dict: + # Ideally this should just return Preferences; maybe hatchway needs a way to + # indicate response models should be serialized by alias? + return schemas.Preferences.from_identity(request.identity).dict(by_alias=True) diff --git a/api/views/tags.py b/api/views/tags.py new file mode 100644 index 0000000..b589ee6 --- /dev/null +++ b/api/views/tags.py @@ -0,0 +1,18 @@ +from django.http import HttpRequest +from hatchway import api_view + +from api import schemas +from api.decorators import scope_required + + +@scope_required("read:follows") +@api_view.get +def followed_tags( + request: HttpRequest, + max_id: str | None = None, + since_id: str | None = None, + min_id: str | None = None, + limit: int = 100, +) -> list[schemas.Tag]: + # We don't implement this yet + return []