Mastodon API / Conversations: Mark the conversation as read for the author when they send a new direct message See merge request pleroma/pleroma!1853merge-requests/1875/head
@@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
- MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities | |||
- OStatus: Extract RSS functionality | |||
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`) | |||
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message | |||
### Fixed | |||
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`) | |||
@@ -48,6 +48,12 @@ defmodule Pleroma.Conversation.Participation do | |||
|> validate_required([:read]) | |||
end | |||
def mark_as_read(%User{} = user, %Conversation{} = conversation) do | |||
with %__MODULE__{} = participation <- for_user_and_conversation(user, conversation) do | |||
mark_as_read(participation) | |||
end | |||
end | |||
def mark_as_read(participation) do | |||
participation | |||
|> read_cng(%{read: true}) | |||
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
alias Pleroma.Activity.Ir.Topics | |||
alias Pleroma.Config | |||
alias Pleroma.Conversation | |||
alias Pleroma.Conversation.Participation | |||
alias Pleroma.Notification | |||
alias Pleroma.Object | |||
alias Pleroma.Object.Containment | |||
@@ -153,11 +154,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
Notification.create_notifications(activity) | |||
participations = | |||
activity | |||
|> Conversation.create_or_bump_for() | |||
|> get_participations() | |||
conversation = create_or_bump_conversation(activity, map["actor"]) | |||
participations = get_participations(conversation) | |||
stream_out(activity) | |||
stream_out_participations(participations) | |||
{:ok, activity} | |||
@@ -182,7 +180,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
end | |||
end | |||
defp get_participations({:ok, %{participations: participations}}), do: participations | |||
defp create_or_bump_conversation(activity, actor) do | |||
with {:ok, conversation} <- Conversation.create_or_bump_for(activity), | |||
%User{} = user <- User.get_cached_by_ap_id(actor), | |||
Participation.mark_as_read(user, conversation) do | |||
{:ok, conversation} | |||
end | |||
end | |||
defp get_participations({:ok, conversation}) do | |||
conversation | |||
|> Repo.preload(:participations, force: true) | |||
|> Map.get(:participations) | |||
end | |||
defp get_participations(_), do: [] | |||
def stream_out_participations(participations) do | |||
@@ -23,6 +23,39 @@ defmodule Pleroma.Conversation.ParticipationTest do | |||
assert %Pleroma.Conversation{} = participation.conversation | |||
end | |||
test "for a new conversation or a reply, it doesn't mark the author's participation as unread" do | |||
user = insert(:user) | |||
other_user = insert(:user) | |||
{:ok, _} = | |||
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"}) | |||
user = User.get_cached_by_id(user.id) | |||
other_user = User.get_cached_by_id(other_user.id) | |||
[%{read: true}] = Participation.for_user(user) | |||
[%{read: false} = participation] = Participation.for_user(other_user) | |||
assert User.get_cached_by_id(user.id).info.unread_conversation_count == 0 | |||
assert User.get_cached_by_id(other_user.id).info.unread_conversation_count == 1 | |||
{:ok, _} = | |||
CommonAPI.post(other_user, %{ | |||
"status" => "Hey @#{user.nickname}.", | |||
"visibility" => "direct", | |||
"in_reply_to_conversation_id" => participation.id | |||
}) | |||
user = User.get_cached_by_id(user.id) | |||
other_user = User.get_cached_by_id(other_user.id) | |||
[%{read: false}] = Participation.for_user(user) | |||
[%{read: true}] = Participation.for_user(other_user) | |||
assert User.get_cached_by_id(user.id).info.unread_conversation_count == 1 | |||
assert User.get_cached_by_id(other_user.id).info.unread_conversation_count == 0 | |||
end | |||
test "for a new conversation, it sets the recipents of the participation" do | |||
user = insert(:user) | |||
other_user = insert(:user) | |||
@@ -32,7 +65,7 @@ defmodule Pleroma.Conversation.ParticipationTest do | |||
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"}) | |||
user = User.get_cached_by_id(user.id) | |||
other_user = User.get_cached_by_id(user.id) | |||
other_user = User.get_cached_by_id(other_user.id) | |||
[participation] = Participation.for_user(user) | |||
participation = Pleroma.Repo.preload(participation, :recipients) | |||
@@ -41,6 +41,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do | |||
assert called(Pleroma.Web.Streamer.stream("participation", participations)) | |||
end | |||
end | |||
test "streams them out on activity creation" do | |||
user_one = insert(:user) | |||
user_two = insert(:user) | |||
with_mock Pleroma.Web.Streamer, | |||
stream: fn _, _ -> nil end do | |||
{:ok, activity} = | |||
CommonAPI.post(user_one, %{ | |||
"status" => "@#{user_two.nickname}", | |||
"visibility" => "direct" | |||
}) | |||
conversation = | |||
activity.data["context"] | |||
|> Pleroma.Conversation.get_for_ap_id() | |||
|> Repo.preload(participations: :user) | |||
assert called(Pleroma.Web.Streamer.stream("participation", conversation.participations)) | |||
end | |||
end | |||
end | |||
describe "fetching restricted by visibility" do | |||
@@ -54,9 +54,9 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
assert user_two.id in account_ids | |||
assert user_three.id in account_ids | |||
assert is_binary(res_id) | |||
assert unread == true | |||
assert unread == false | |||
assert res_last_status["id"] == direct.id | |||
assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1 | |||
assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0 | |||
end | |||
test "updates the last_status on reply", %{conn: conn} do | |||
@@ -95,19 +95,23 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
"visibility" => "direct" | |||
}) | |||
assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0 | |||
assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 1 | |||
[%{"id" => direct_conversation_id, "unread" => true}] = | |||
conn | |||
|> assign(:user, user_one) | |||
|> assign(:user, user_two) | |||
|> get("/api/v1/conversations") | |||
|> json_response(200) | |||
%{"unread" => false} = | |||
conn | |||
|> assign(:user, user_one) | |||
|> assign(:user, user_two) | |||
|> post("/api/v1/conversations/#{direct_conversation_id}/read") | |||
|> json_response(200) | |||
assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0 | |||
assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0 | |||
# The conversation is marked as unread on reply | |||
{:ok, _} = | |||
@@ -124,6 +128,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
|> json_response(200) | |||
assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1 | |||
assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0 | |||
# A reply doesn't increment the user's unread_conversation_count if the conversation is unread | |||
{:ok, _} = | |||
@@ -134,6 +139,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do | |||
}) | |||
assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1 | |||
assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0 | |||
end | |||
test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do | |||
@@ -424,8 +424,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do | |||
other_user = insert(:user) | |||
{:ok, _activity} = | |||
CommonAPI.post(user, %{ | |||
"status" => "Hey @#{other_user.nickname}.", | |||
CommonAPI.post(other_user, %{ | |||
"status" => "Hey @#{user.nickname}.", | |||
"visibility" => "direct" | |||
}) | |||