Extend Mastodon API with public endpoint for getting Favorites timeline of any user (#789)
This commit is contained in:
parent
10c40e13d2
commit
9dd36e5bcb
|
@ -16,12 +16,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Configuration: `link_name` option
|
- Configuration: `link_name` option
|
||||||
- Configuration: `fetch_initial_posts` option
|
- Configuration: `fetch_initial_posts` option
|
||||||
- Configuration: `notify_email` option
|
- Configuration: `notify_email` option
|
||||||
- Pleroma API: User subscribtions
|
- Pleroma API: User subscriptions
|
||||||
- Pleroma API: Healthcheck endpoint
|
- Pleroma API: Healthcheck endpoint
|
||||||
- Admin API: Endpoints for listing/revoking invite tokens
|
- Admin API: Endpoints for listing/revoking invite tokens
|
||||||
- Admin API: Endpoints for making users follow/unfollow each other
|
- Admin API: Endpoints for making users follow/unfollow each other
|
||||||
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
- Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
|
||||||
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
- Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
|
||||||
|
- Mastodon API: `/api/v1/pleroma/accounts/:id/favourites` (API extension)
|
||||||
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/)
|
||||||
- ActivityPub C2S: OAuth endpoints
|
- ActivityPub C2S: OAuth endpoints
|
||||||
- Metadata RelMe provider
|
- Metadata RelMe provider
|
||||||
|
|
|
@ -77,7 +77,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
|
||||||
* `token`: invite token required when the registrations aren't public.
|
* `token`: invite token required when the registrations aren't public.
|
||||||
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
|
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}`
|
||||||
* Example response:
|
* Example response:
|
||||||
```
|
```json
|
||||||
{
|
{
|
||||||
"background_image": null,
|
"background_image": null,
|
||||||
"cover_photo": "https://pleroma.soykaf.com/images/banner.png",
|
"cover_photo": "https://pleroma.soykaf.com/images/banner.png",
|
||||||
|
@ -187,6 +187,62 @@ See [Admin-API](Admin-API.md)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `/api/v1/pleroma/accounts/:id/favourites`
|
||||||
|
### Returns favorites timeline of any user
|
||||||
|
* Method `GET`
|
||||||
|
* Authentication: not required
|
||||||
|
* Params:
|
||||||
|
* `id`: the id of the account for whom to return results
|
||||||
|
* `limit`: optional, the number of records to retrieve
|
||||||
|
* `since_id`: optional, returns results that are more recent than the specified id
|
||||||
|
* `max_id`: optional, returns results that are older than the specified id
|
||||||
|
* Response: JSON, returns a list of Mastodon Status entities on success, otherwise returns `{"error": "error_msg"}`
|
||||||
|
* Example response:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"account": {
|
||||||
|
"id": "9hptFmUF3ztxYh3Svg",
|
||||||
|
"url": "https://pleroma.example.org/users/nick2",
|
||||||
|
"username": "nick2",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"application": {"name": "Web", "website": null},
|
||||||
|
"bookmarked": false,
|
||||||
|
"card": null,
|
||||||
|
"content": "This is :moominmamma: note 0",
|
||||||
|
"created_at": "2019-04-15T15:42:15.000Z",
|
||||||
|
"emojis": [],
|
||||||
|
"favourited": false,
|
||||||
|
"favourites_count": 1,
|
||||||
|
"id": "9hptFmVJ02khbzYJaS",
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"language": null,
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": [],
|
||||||
|
"muted": false,
|
||||||
|
"pinned": false,
|
||||||
|
"pleroma": {
|
||||||
|
"content": {"text/plain": "This is :moominmamma: note 0"},
|
||||||
|
"conversation_id": 13679,
|
||||||
|
"local": true,
|
||||||
|
"spoiler_text": {"text/plain": "2hu"}
|
||||||
|
},
|
||||||
|
"reblog": null,
|
||||||
|
"reblogged": false,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"replies_count": 0,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "2hu",
|
||||||
|
"tags": [{"name": "2hu", "url": "/tag/2hu"}],
|
||||||
|
"uri": "https://pleroma.example.org/objects/198ed2a1-7912-4482-b559-244a0369e984",
|
||||||
|
"url": "https://pleroma.example.org/notice/9hptFmVJ02khbzYJaS",
|
||||||
|
"visibility": "public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## `/api/pleroma/notification_settings`
|
## `/api/pleroma/notification_settings`
|
||||||
### Updates user notification settings
|
### Updates user notification settings
|
||||||
* Method `PUT`
|
* Method `PUT`
|
||||||
|
|
|
@ -38,6 +38,7 @@ defmodule Pleroma.User.Info do
|
||||||
field(:salmon, :string, default: nil)
|
field(:salmon, :string, default: nil)
|
||||||
field(:hide_followers, :boolean, default: false)
|
field(:hide_followers, :boolean, default: false)
|
||||||
field(:hide_follows, :boolean, default: false)
|
field(:hide_follows, :boolean, default: false)
|
||||||
|
field(:hide_favorites, :boolean, default: true)
|
||||||
field(:pinned_activities, {:array, :string}, default: [])
|
field(:pinned_activities, {:array, :string}, default: [])
|
||||||
field(:flavour, :string, default: nil)
|
field(:flavour, :string, default: nil)
|
||||||
|
|
||||||
|
@ -202,6 +203,7 @@ def profile_update(info, params) do
|
||||||
:banner,
|
:banner,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
|
:hide_favorites,
|
||||||
:background,
|
:background,
|
||||||
:show_role
|
:show_role
|
||||||
])
|
])
|
||||||
|
|
|
@ -1087,6 +1087,43 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||||
|
with %User{} = user <- User.get_by_id(id),
|
||||||
|
false <- user.info.hide_favorites do
|
||||||
|
params =
|
||||||
|
params
|
||||||
|
|> Map.put("type", "Create")
|
||||||
|
|> Map.put("favorited_by", user.ap_id)
|
||||||
|
|> Map.put("blocking_user", for_user)
|
||||||
|
|
||||||
|
recipients =
|
||||||
|
if for_user do
|
||||||
|
["https://www.w3.org/ns/activitystreams#Public"] ++
|
||||||
|
[for_user.ap_id | for_user.following]
|
||||||
|
else
|
||||||
|
["https://www.w3.org/ns/activitystreams#Public"]
|
||||||
|
end
|
||||||
|
|
||||||
|
activities =
|
||||||
|
recipients
|
||||||
|
|> ActivityPub.fetch_activities(params)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(:favourites, activities)
|
||||||
|
|> put_view(StatusView)
|
||||||
|
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
|
||||||
|
else
|
||||||
|
nil ->
|
||||||
|
{:error, :not_found}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
conn
|
||||||
|
|> put_status(403)
|
||||||
|
|> json(%{error: "Can't get favorites"})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
||||||
user = User.get_cached_by_id(user.id)
|
user = User.get_cached_by_id(user.id)
|
||||||
|
|
||||||
|
|
|
@ -395,6 +395,8 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/accounts/:id", MastodonAPIController, :user)
|
get("/accounts/:id", MastodonAPIController, :user)
|
||||||
|
|
||||||
get("/search", MastodonAPIController, :search)
|
get("/search", MastodonAPIController, :search)
|
||||||
|
|
||||||
|
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -632,7 +632,7 @@ def raw_empty_array(conn, _params) do
|
||||||
|
|
||||||
defp build_info_cng(user, params) do
|
defp build_info_cng(user, params) do
|
||||||
info_params =
|
info_params =
|
||||||
["no_rich_text", "locked", "hide_followers", "hide_follows", "show_role"]
|
["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]
|
||||||
|> Enum.reduce(%{}, fn key, res ->
|
|> Enum.reduce(%{}, fn key, res ->
|
||||||
if value = params[key] do
|
if value = params[key] do
|
||||||
Map.put(res, key, value == "true")
|
Map.put(res, key, value == "true")
|
||||||
|
|
|
@ -1988,6 +1988,199 @@ test "returns the favorites of a user", %{conn: conn} do
|
||||||
assert [] = json_response(third_conn, 200)
|
assert [] = json_response(third_conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "getting favorites timeline of specified user" do
|
||||||
|
setup do
|
||||||
|
[current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
|
||||||
|
[current_user: current_user, user: user]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns list of statuses favorited by specified user", %{
|
||||||
|
conn: conn,
|
||||||
|
current_user: current_user,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
[activity | _] = insert_pair(:note_activity)
|
||||||
|
CommonAPI.favorite(activity.id, user)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
[like] = response
|
||||||
|
|
||||||
|
assert length(response) == 1
|
||||||
|
assert like["id"] == activity.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns favorites for specified user_id when user is not logged in", %{
|
||||||
|
conn: conn,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
CommonAPI.favorite(activity.id, user)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert length(response) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns favorited DM only when user is logged in and he is one of recipients", %{
|
||||||
|
conn: conn,
|
||||||
|
current_user: current_user,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
{:ok, direct} =
|
||||||
|
CommonAPI.post(current_user, %{
|
||||||
|
"status" => "Hi @#{user.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
CommonAPI.favorite(direct.id, user)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert length(response) == 1
|
||||||
|
|
||||||
|
anonymous_response =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert length(anonymous_response) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not return others' favorited DM when user is not one of recipients", %{
|
||||||
|
conn: conn,
|
||||||
|
current_user: current_user,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
user_two = insert(:user)
|
||||||
|
|
||||||
|
{:ok, direct} =
|
||||||
|
CommonAPI.post(user_two, %{
|
||||||
|
"status" => "Hi @#{user.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
CommonAPI.favorite(direct.id, user)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert length(response) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "paginates favorites using since_id and max_id", %{
|
||||||
|
conn: conn,
|
||||||
|
current_user: current_user,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
activities = insert_list(10, :note_activity)
|
||||||
|
|
||||||
|
Enum.each(activities, fn activity ->
|
||||||
|
CommonAPI.favorite(activity.id, user)
|
||||||
|
end)
|
||||||
|
|
||||||
|
third_activity = Enum.at(activities, 2)
|
||||||
|
seventh_activity = Enum.at(activities, 6)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
|
||||||
|
since_id: third_activity.id,
|
||||||
|
max_id: seventh_activity.id
|
||||||
|
})
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert length(response) == 3
|
||||||
|
refute third_activity in response
|
||||||
|
refute seventh_activity in response
|
||||||
|
end
|
||||||
|
|
||||||
|
test "limits favorites using limit parameter", %{
|
||||||
|
conn: conn,
|
||||||
|
current_user: current_user,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
7
|
||||||
|
|> insert_list(:note_activity)
|
||||||
|
|> Enum.each(fn activity ->
|
||||||
|
CommonAPI.favorite(activity.id, user)
|
||||||
|
end)
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert length(response) == 3
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns empty response when user does not have any favorited statuses", %{
|
||||||
|
conn: conn,
|
||||||
|
current_user: current_user,
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert Enum.empty?(response)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 404 error when specified user is not exist", %{conn: conn} do
|
||||||
|
conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
|
||||||
|
|
||||||
|
assert json_response(conn, 404) == %{"error" => "Record not found"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns 403 error when user has hidden own favorites", %{
|
||||||
|
conn: conn,
|
||||||
|
current_user: current_user
|
||||||
|
} do
|
||||||
|
user = insert(:user, %{info: %{hide_favorites: true}})
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
CommonAPI.favorite(activity.id, user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|
||||||
|
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
|
||||||
|
user = insert(:user)
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
CommonAPI.favorite(activity.id, user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, current_user)
|
||||||
|
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||||
|
|
||||||
|
assert user.info.hide_favorites
|
||||||
|
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "updating credentials" do
|
describe "updating credentials" do
|
||||||
test "updates the user's bio", %{conn: conn} do
|
test "updates the user's bio", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
Loading…
Reference in a new issue