@@ -16,12 +16,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
- Configuration: `link_name` option | |||
- Configuration: `fetch_initial_posts` option | |||
- Configuration: `notify_email` option | |||
- Pleroma API: User subscribtions | |||
- Pleroma API: User subscriptions | |||
- Pleroma API: Healthcheck endpoint | |||
- Admin API: Endpoints for listing/revoking invite tokens | |||
- Admin API: Endpoints for making users follow/unfollow each other | |||
- 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/pleroma/accounts/:id/favourites` (API extension) | |||
- Mastodon API: [Reports](https://docs.joinmastodon.org/api/rest/reports/) | |||
- ActivityPub C2S: OAuth endpoints | |||
- 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. | |||
* Response: JSON. Returns a user object on success, otherwise returns `{"error": "error_msg"}` | |||
* Example response: | |||
``` | |||
```json | |||
{ | |||
"background_image": null, | |||
"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` | |||
### Updates user notification settings | |||
* Method `PUT` | |||
@@ -38,6 +38,7 @@ defmodule Pleroma.User.Info do | |||
field(:salmon, :string, default: nil) | |||
field(:hide_followers, :boolean, default: false) | |||
field(:hide_follows, :boolean, default: false) | |||
field(:hide_favorites, :boolean, default: true) | |||
field(:pinned_activities, {:array, :string}, default: []) | |||
field(:flavour, :string, default: nil) | |||
@@ -202,6 +203,7 @@ defmodule Pleroma.User.Info do | |||
:banner, | |||
:hide_follows, | |||
:hide_followers, | |||
:hide_favorites, | |||
:background, | |||
:show_role | |||
]) | |||
@@ -1087,6 +1087,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
|> render("index.json", %{activities: activities, for: user, as: :activity}) | |||
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 | |||
user = User.get_cached_by_id(user.id) | |||
@@ -395,6 +395,8 @@ defmodule Pleroma.Web.Router do | |||
get("/accounts/:id", MastodonAPIController, :user) | |||
get("/search", MastodonAPIController, :search) | |||
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites) | |||
end | |||
end | |||
@@ -632,7 +632,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
defp build_info_cng(user, params) do | |||
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 -> | |||
if value = params[key] do | |||
Map.put(res, key, value == "true") | |||
@@ -1988,6 +1988,199 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
assert [] = json_response(third_conn, 200) | |||
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 | |||
test "updates the user's bio", %{conn: conn} do | |||
user = insert(:user) | |||