From dd3fc50ea41871c6c02076cf2786c2488d4cf3ca Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 Jan 2020 13:57:42 +0100 Subject: [PATCH 1/2] Emoji reactions: Change cache and reply format --- lib/pleroma/web/activity_pub/utils.ex | 64 ++++++++++++------- .../web/mastodon_api/views/status_view.ex | 7 +- .../controllers/pleroma_api_controller.ex | 8 +-- test/web/activity_pub/activity_pub_test.exs | 20 +++++- .../mastodon_api/views/status_view_test.exs | 7 +- .../pleroma_api_controller_test.exs | 4 +- 6 files changed, 70 insertions(+), 40 deletions(-) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index db7084246..4def431f1 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -312,19 +312,12 @@ def make_emoji_reaction_data(user, object, emoji, activity_id) do |> Map.put("content", emoji) end - @spec update_element_in_object(String.t(), list(any), Object.t()) :: + @spec update_element_in_object(String.t(), list(any), Object.t(), integer() | nil) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} - def update_element_in_object(property, element, object) do + def update_element_in_object(property, element, object, count \\ nil) do length = - if is_map(element) do - element - |> Map.values() - |> List.flatten() - |> length() - else - element - |> length() - end + count || + length(element) data = Map.merge( @@ -344,29 +337,52 @@ def add_emoji_reaction_to_object( %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || %{} - emoji_actors = reactions[emoji] || [] - new_emoji_actors = [actor | emoji_actors] |> Enum.uniq() - new_reactions = Map.put(reactions, emoji, new_emoji_actors) - update_element_in_object("reaction", new_reactions, object) + reactions = object.data["reactions"] || [] + + new_reactions = + case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + nil -> + reactions ++ [[emoji, [actor]]] + + index -> + List.update_at( + reactions, + index, + fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end + ) + end + + count = emoji_count(new_reactions) + + update_element_in_object("reaction", new_reactions, object, count) + end + + def emoji_count(reactions_list) do + Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end) end def remove_emoji_reaction_from_object( %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || %{} - emoji_actors = reactions[emoji] || [] - new_emoji_actors = List.delete(emoji_actors, actor) + reactions = object.data["reactions"] || [] new_reactions = - if new_emoji_actors == [] do - Map.delete(reactions, emoji) - else - Map.put(reactions, emoji, new_emoji_actors) + case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + nil -> + reactions + + index -> + List.update_at( + reactions, + index, + fn [emoji, users] -> [emoji, List.delete(users, actor)] end + ) + |> Enum.reject(fn [_, users] -> Enum.empty?(users) end) end - update_element_in_object("reaction", new_reactions, object) + count = emoji_count(new_reactions) + update_element_in_object("reaction", new_reactions, object, count) end @spec add_like_to_object(Activity.t(), Object.t()) :: diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index b59ac39bc..64a97896a 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -255,12 +255,11 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} emoji_reactions = with %{data: %{"reactions" => emoji_reactions}} <- object do - Enum.map(emoji_reactions, fn {emoji, users} -> - {emoji, length(users)} + Enum.map(emoji_reactions, fn [emoji, users] -> + [emoji, length(users)] end) - |> Enum.into(%{}) else - _ -> %{} + _ -> [] end %{ diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 3285dc11b..bb19836ae 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -43,21 +43,21 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), - %Object{data: %{"reactions" => emoji_reactions}} <- Object.normalize(activity) do + %Object{data: %{"reactions" => emoji_reactions}} when is_list(emoji_reactions) <- + Object.normalize(activity) do reactions = emoji_reactions - |> Enum.map(fn {emoji, users} -> + |> Enum.map(fn [emoji, users] -> users = Enum.map(users, &User.get_cached_by_ap_id/1) {emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})} end) - |> Enum.into(%{}) conn |> json(reactions) else _e -> conn - |> json(%{}) + |> json([]) end end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index ad6b9810c..ff4604a52 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -867,6 +867,8 @@ test "returns reblogs for users for whom reblogs have not been muted" do test "adds an emoji reaction activity to the db" do user = insert(:user) reactor = insert(:user) + third_user = insert(:user) + fourth_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) assert object = Object.normalize(activity) @@ -881,7 +883,21 @@ test "adds an emoji reaction activity to the db" do assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]] assert reaction_activity.data["context"] == object.data["context"] assert object.data["reaction_count"] == 1 - assert object.data["reactions"]["🔥"] == [reactor.ap_id] + assert object.data["reactions"] == [["🔥", [reactor.ap_id]]] + + {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(third_user, object, "☕") + + assert object.data["reaction_count"] == 2 + assert object.data["reactions"] == [["🔥", [reactor.ap_id]], ["☕", [third_user.ap_id]]] + + {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(fourth_user, object, "🔥") + + assert object.data["reaction_count"] == 3 + + assert object.data["reactions"] == [ + ["🔥", [fourth_user.ap_id, reactor.ap_id]], + ["☕", [third_user.ap_id]] + ] end end @@ -919,7 +935,7 @@ test "adds an undo activity to the db" do object = Object.get_by_ap_id(object.data["id"]) assert object.data["reaction_count"] == 0 - assert object.data["reactions"] == %{} + assert object.data["reactions"] == [] end end diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index b54b19c0b..069bb8eac 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -31,13 +31,12 @@ test "has an emoji reaction list" do {:ok, activity} = CommonAPI.post(user, %{"status" => "dae cofe??"}) {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "☕") - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵") + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") activity = Repo.get(Activity, activity.id) status = StatusView.render("show.json", activity: activity) - assert status[:pleroma][:emoji_reactions]["🍵"] == 1 - assert status[:pleroma][:emoji_reactions]["☕"] == 2 + assert status[:pleroma][:emoji_reactions] == [["☕", 2], ["🍵", 1]] end test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do @@ -189,7 +188,7 @@ test "a note activity" do expires_at: nil, direct_conversation_id: nil, thread_muted: false, - emoji_reactions: %{} + emoji_reactions: [] } } diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index fb7500134..a79ecd05b 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -62,7 +62,7 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") |> json_response(200) - assert result == %{} + assert result == [] {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") @@ -71,7 +71,7 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") |> json_response(200) - [represented_user] = result["🎅"] + [["🎅", [represented_user]]] = result assert represented_user["id"] == other_user.id end From 043cd5aa5458bb5b2ae0b44e475db59c195bec24 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 22 Jan 2020 13:58:02 +0100 Subject: [PATCH 2/2] Emoji reactions: Document changes --- CHANGELOG.md | 1 + docs/API/differences_in_mastoapi_responses.md | 2 +- docs/API/pleroma_api.md | 10 +++++----- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f31d5a9..0bc555878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Captcha: Enable by default - Mastodon API: Add support for `account_id` param to filter notifications by the account - Mastodon API: Add `emoji_reactions` property to Statuses +- Mastodon API: Change emoji reaction reply format ### Fixed diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 50076cf98..3f75a13f7 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -29,7 +29,7 @@ Has these additional fields under the `pleroma` object: - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire - `thread_muted`: true if the thread the post belongs to is muted -- `emoji_reactions`: An object with all the emoji reactions with count. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint. +- `emoji_reactions`: A list with emoji / reaction count tuples. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint. ## Attachments diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 689edbcc2..9ca8a5af0 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -451,11 +451,11 @@ Emoji reactions work a lot like favourites do. They make it possible to react to * Method: `GET` * Authentication: optional * Params: None -* Response: JSON, a map of emoji to account list mappings. +* Response: JSON, a list of emoji/account list tuples, sorted by emoji insertion date, in ascending order, e.g, the first emoji in the list is the oldest. * Example Response: ```json -{ - "😀" => [{"id" => "xyz.."...}, {"id" => "zyx..."}], - "🗡" => [{"id" => "abc..."}] -} +[ + ["😀", [{"id" => "xyz.."...}, {"id" => "zyx..."}]], + ["☕", [{"id" => "abc..."}]] +] ```