forked from AkkomaGang/akkoma
Mastodon API: Mark the conversation as read for the author when they send a new direct message
This commit is contained in:
parent
1bfdf57fc7
commit
359dd1890e
7 changed files with 91 additions and 13 deletions
|
@ -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
|
- MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
|
||||||
- OStatus: Extract RSS functionality
|
- OStatus: Extract RSS functionality
|
||||||
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
|
- 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
|
### 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`)
|
- 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 @@ def read_cng(struct, params) do
|
||||||
|> validate_required([:read])
|
|> validate_required([:read])
|
||||||
end
|
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
|
def mark_as_read(participation) do
|
||||||
participation
|
participation
|
||||||
|> read_cng(%{read: true})
|
|> read_cng(%{read: true})
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
alias Pleroma.Activity.Ir.Topics
|
alias Pleroma.Activity.Ir.Topics
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
alias Pleroma.Conversation
|
alias Pleroma.Conversation
|
||||||
|
alias Pleroma.Conversation.Participation
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
|
@ -153,11 +154,8 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
|
||||||
|
|
||||||
Notification.create_notifications(activity)
|
Notification.create_notifications(activity)
|
||||||
|
|
||||||
participations =
|
conversation = create_or_bump_conversation(activity, map["actor"])
|
||||||
activity
|
participations = get_participations(conversation)
|
||||||
|> Conversation.create_or_bump_for()
|
|
||||||
|> get_participations()
|
|
||||||
|
|
||||||
stream_out(activity)
|
stream_out(activity)
|
||||||
stream_out_participations(participations)
|
stream_out_participations(participations)
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
|
@ -182,7 +180,20 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
|
||||||
end
|
end
|
||||||
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: []
|
defp get_participations(_), do: []
|
||||||
|
|
||||||
def stream_out_participations(participations) do
|
def stream_out_participations(participations) do
|
||||||
|
|
|
@ -23,6 +23,39 @@ test "getting a participation will also preload things" do
|
||||||
assert %Pleroma.Conversation{} = participation.conversation
|
assert %Pleroma.Conversation{} = participation.conversation
|
||||||
end
|
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
|
test "for a new conversation, it sets the recipents of the participation" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
@ -32,7 +65,7 @@ test "for a new conversation, it sets the recipents of the participation" do
|
||||||
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"})
|
CommonAPI.post(user, %{"status" => "Hey @#{other_user.nickname}.", "visibility" => "direct"})
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
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] = Participation.for_user(user)
|
||||||
participation = Pleroma.Repo.preload(participation, :recipients)
|
participation = Pleroma.Repo.preload(participation, :recipients)
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,27 @@ test "it streams them out" do
|
||||||
assert called(Pleroma.Web.Streamer.stream("participation", participations))
|
assert called(Pleroma.Web.Streamer.stream("participation", participations))
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
describe "fetching restricted by visibility" do
|
describe "fetching restricted by visibility" do
|
||||||
|
|
|
@ -54,9 +54,9 @@ test "returns a list of conversations", %{conn: conn} do
|
||||||
assert user_two.id in account_ids
|
assert user_two.id in account_ids
|
||||||
assert user_three.id in account_ids
|
assert user_three.id in account_ids
|
||||||
assert is_binary(res_id)
|
assert is_binary(res_id)
|
||||||
assert unread == true
|
assert unread == false
|
||||||
assert res_last_status["id"] == direct.id
|
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
|
end
|
||||||
|
|
||||||
test "updates the last_status on reply", %{conn: conn} do
|
test "updates the last_status on reply", %{conn: conn} do
|
||||||
|
@ -95,19 +95,23 @@ test "the user marks a conversation as read", %{conn: conn} do
|
||||||
"visibility" => "direct"
|
"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}] =
|
[%{"id" => direct_conversation_id, "unread" => true}] =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user_one)
|
|> assign(:user, user_two)
|
||||||
|> get("/api/v1/conversations")
|
|> get("/api/v1/conversations")
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
%{"unread" => false} =
|
%{"unread" => false} =
|
||||||
conn
|
conn
|
||||||
|> assign(:user, user_one)
|
|> assign(:user, user_two)
|
||||||
|> post("/api/v1/conversations/#{direct_conversation_id}/read")
|
|> post("/api/v1/conversations/#{direct_conversation_id}/read")
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0
|
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
|
# The conversation is marked as unread on reply
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
|
@ -124,6 +128,7 @@ test "the user marks a conversation as read", %{conn: conn} do
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
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 == 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
|
# A reply doesn't increment the user's unread_conversation_count if the conversation is unread
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
|
@ -134,6 +139,7 @@ test "the user marks a conversation as read", %{conn: conn} do
|
||||||
})
|
})
|
||||||
|
|
||||||
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 == 1
|
||||||
|
assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do
|
test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do
|
||||||
|
|
|
@ -424,8 +424,8 @@ test "shows unread_conversation_count only to the account owner" do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
||||||
{:ok, _activity} =
|
{:ok, _activity} =
|
||||||
CommonAPI.post(user, %{
|
CommonAPI.post(other_user, %{
|
||||||
"status" => "Hey @#{other_user.nickname}.",
|
"status" => "Hey @#{user.nickname}.",
|
||||||
"visibility" => "direct"
|
"visibility" => "direct"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue