forked from AkkomaGang/akkoma
Merge branch 'features/private-reblogs' into 'develop'
Allow receiving private self-announces over ActivityPub See merge request pleroma/pleroma!1766
This commit is contained in:
commit
9b38bf4af4
9 changed files with 115 additions and 9 deletions
|
@ -346,7 +346,7 @@ def announce(
|
||||||
local \\ true,
|
local \\ true,
|
||||||
public \\ true
|
public \\ true
|
||||||
) do
|
) do
|
||||||
with true <- is_public?(object),
|
with true <- is_announceable?(object, user, public),
|
||||||
announce_data <- make_announce_data(user, object, activity_id, public),
|
announce_data <- make_announce_data(user, object, activity_id, public),
|
||||||
{:ok, activity} <- insert(announce_data, local),
|
{:ok, activity} <- insert(announce_data, local),
|
||||||
{:ok, object} <- add_announce_to_object(activity, object),
|
{:ok, object} <- add_announce_to_object(activity, object),
|
||||||
|
|
|
@ -494,7 +494,7 @@ def make_unlike_data(
|
||||||
@spec add_announce_to_object(Activity.t(), Object.t()) ::
|
@spec add_announce_to_object(Activity.t(), Object.t()) ::
|
||||||
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
|
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def add_announce_to_object(
|
def add_announce_to_object(
|
||||||
%Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}},
|
%Activity{data: %{"actor" => actor}},
|
||||||
object
|
object
|
||||||
) do
|
) do
|
||||||
announcements = take_announcements(object)
|
announcements = take_announcements(object)
|
||||||
|
|
|
@ -27,6 +27,11 @@ def is_private?(activity) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_announceable?(activity, user, public \\ true) do
|
||||||
|
is_public?(activity) ||
|
||||||
|
(!public && is_private?(activity) && activity.data["actor"] == user.ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
|
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
|
||||||
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
|
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
|
||||||
|
|
||||||
|
|
|
@ -76,11 +76,12 @@ def delete(activity_id, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def repeat(id_or_ap_id, user) do
|
def repeat(id_or_ap_id, user, params \\ %{}) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
object <- Object.normalize(activity),
|
object <- Object.normalize(activity),
|
||||||
nil <- Utils.get_existing_announce(user.ap_id, object) do
|
nil <- Utils.get_existing_announce(user.ap_id, object),
|
||||||
ActivityPub.announce(user, object)
|
public <- get_announce_visibility(object, params) do
|
||||||
|
ActivityPub.announce(user, object, nil, true, public)
|
||||||
else
|
else
|
||||||
_ -> {:error, dgettext("errors", "Could not repeat")}
|
_ -> {:error, dgettext("errors", "Could not repeat")}
|
||||||
end
|
end
|
||||||
|
@ -169,6 +170,14 @@ defp normalize_and_validate_choices(choices, object) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_announce_visibility(_, %{"visibility" => visibility})
|
||||||
|
when visibility in ~w{public unlisted private direct},
|
||||||
|
do: visibility in ~w(public unlisted)
|
||||||
|
|
||||||
|
def get_announce_visibility(object, _) do
|
||||||
|
Visibility.is_public?(object)
|
||||||
|
end
|
||||||
|
|
||||||
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
|
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
|
||||||
|
|
||||||
def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
|
def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
|
||||||
|
|
|
@ -125,8 +125,8 @@ def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "POST /api/v1/statuses/:id/reblog"
|
@doc "POST /api/v1/statuses/:id/reblog"
|
||||||
def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do
|
||||||
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
|
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params),
|
||||||
%Activity{} = announce <- Activity.normalize(announce.data) do
|
%Activity{} = announce <- Activity.normalize(announce.data) do
|
||||||
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
|
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
|
||||||
end
|
end
|
||||||
|
@ -242,7 +242,19 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
|
||||||
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
|
%Object{data: %{"announcements" => announces, "id" => ap_id}} <-
|
||||||
|
Object.normalize(activity) do
|
||||||
|
announces =
|
||||||
|
"Announce"
|
||||||
|
|> Activity.Queries.by_type()
|
||||||
|
|> Ecto.Query.where([a], a.actor in ^announces)
|
||||||
|
# this is to use the index
|
||||||
|
|> Activity.Queries.by_object_id(ap_id)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(&Visibility.visible_for_user?(&1, user))
|
||||||
|
|> Enum.map(& &1.actor)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|
||||||
users =
|
users =
|
||||||
User
|
User
|
||||||
|> Ecto.Query.where([u], u.ap_id in ^announces)
|
|> Ecto.Query.where([u], u.ap_id in ^announces)
|
||||||
|
|
|
@ -125,7 +125,7 @@ def render(
|
||||||
pinned: pinned?(activity, user),
|
pinned: pinned?(activity, user),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
spoiler_text: "",
|
spoiler_text: "",
|
||||||
visibility: "public",
|
visibility: get_visibility(activity),
|
||||||
media_attachments: reblogged[:media_attachments] || [],
|
media_attachments: reblogged[:media_attachments] || [],
|
||||||
mentions: mentions,
|
mentions: mentions,
|
||||||
tags: reblogged[:tags] || [],
|
tags: reblogged[:tags] || [],
|
||||||
|
|
|
@ -839,6 +839,39 @@ test "adds an announce activity to the db" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "announcing a private object" do
|
||||||
|
test "adds an announce activity to the db if the audience is not widened" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
|
||||||
|
object = Object.normalize(note_activity)
|
||||||
|
|
||||||
|
{:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false)
|
||||||
|
|
||||||
|
assert announce_activity.data["to"] == [User.ap_followers(user)]
|
||||||
|
|
||||||
|
assert announce_activity.data["object"] == object.data["id"]
|
||||||
|
assert announce_activity.data["actor"] == user.ap_id
|
||||||
|
assert announce_activity.data["context"] == object.data["context"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not add an announce activity to the db if the audience is widened" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
|
||||||
|
object = Object.normalize(note_activity)
|
||||||
|
|
||||||
|
assert {:error, _} = ActivityPub.announce(user, object, nil, true, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not add an announce activity to the db if the announcer is not the author" do
|
||||||
|
user = insert(:user)
|
||||||
|
announcer = insert(:user)
|
||||||
|
{:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
|
||||||
|
object = Object.normalize(note_activity)
|
||||||
|
|
||||||
|
assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "unannouncing an object" do
|
describe "unannouncing an object" do
|
||||||
test "unannouncing a previously announced object" do
|
test "unannouncing a previously announced object" do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
|
|
|
@ -231,6 +231,18 @@ test "repeating a status" do
|
||||||
{:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
|
{:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "repeating a status privately" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||||
|
|
||||||
|
{:ok, %Activity{} = announce_activity, _} =
|
||||||
|
CommonAPI.repeat(activity.id, user, %{"visibility" => "private"})
|
||||||
|
|
||||||
|
assert Visibility.is_private?(announce_activity)
|
||||||
|
end
|
||||||
|
|
||||||
test "favoriting a status" do
|
test "favoriting a status" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
|
|
|
@ -547,6 +547,24 @@ test "reblogs and returns the reblogged status", %{conn: conn} do
|
||||||
assert to_string(activity.id) == id
|
assert to_string(activity.id) == id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "reblogs privately and returns the reblogged status", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"})
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
|
||||||
|
"reblogged" => true,
|
||||||
|
"visibility" => "private"
|
||||||
|
} = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert to_string(activity.id) == id
|
||||||
|
end
|
||||||
|
|
||||||
test "reblogged status for another user", %{conn: conn} do
|
test "reblogged status for another user", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
user1 = insert(:user)
|
user1 = insert(:user)
|
||||||
|
@ -1149,6 +1167,23 @@ test "does not return users who have reblogged the status but are blocked", %{
|
||||||
assert Enum.empty?(response)
|
assert Enum.empty?(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not return users who have reblogged the status privately", %{
|
||||||
|
conn: %{assigns: %{user: user}} = conn,
|
||||||
|
activity: activity
|
||||||
|
} do
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"})
|
||||||
|
|
||||||
|
response =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
||||||
|
|> json_response(:ok)
|
||||||
|
|
||||||
|
assert Enum.empty?(response)
|
||||||
|
end
|
||||||
|
|
||||||
test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
|
test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
|
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
|
||||||
|
|
Loading…
Reference in a new issue