Merge branch 'csaurus/pleroma-feature/mstdn-direct-api' into develop
This commit is contained in:
commit
46cc6fab07
|
@ -53,15 +53,24 @@ def insert(map, local \\ true) when is_map(map) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def stream_out(activity) do
|
def stream_out(activity) do
|
||||||
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
if activity.data["type"] in ["Create", "Announce"] do
|
if activity.data["type"] in ["Create", "Announce"] do
|
||||||
Pleroma.Web.Streamer.stream("user", activity)
|
Pleroma.Web.Streamer.stream("user", activity)
|
||||||
|
|
||||||
if Enum.member?(activity.data["to"], "https://www.w3.org/ns/activitystreams#Public") do
|
if Enum.member?(activity.data["to"], public) do
|
||||||
Pleroma.Web.Streamer.stream("public", activity)
|
Pleroma.Web.Streamer.stream("public", activity)
|
||||||
|
|
||||||
if activity.local do
|
if activity.local do
|
||||||
Pleroma.Web.Streamer.stream("public:local", activity)
|
Pleroma.Web.Streamer.stream("public:local", activity)
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
if !Enum.member?(activity.data["cc"] || [], public) &&
|
||||||
|
!Enum.member?(
|
||||||
|
activity.data["to"],
|
||||||
|
User.get_by_ap_id(activity.data["actor"]).follower_address
|
||||||
|
),
|
||||||
|
do: Pleroma.Web.Streamer.stream("direct", activity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -293,6 +302,32 @@ def fetch_public_activities(opts \\ %{}) do
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@valid_visibilities ~w[direct unlisted public private]
|
||||||
|
|
||||||
|
defp restrict_visibility(query, %{visibility: "direct"}) do
|
||||||
|
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
|
from(
|
||||||
|
activity in query,
|
||||||
|
join: sender in User,
|
||||||
|
on: sender.ap_id == activity.actor,
|
||||||
|
# Are non-direct statuses with no to/cc possible?
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"not (? && ?)",
|
||||||
|
[^public, sender.follower_address],
|
||||||
|
activity.recipients
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_visibility(_query, %{visibility: visibility})
|
||||||
|
when visibility not in @valid_visibilities do
|
||||||
|
Logger.error("Could not restrict visibility to #{visibility}")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp restrict_visibility(query, _visibility), do: query
|
||||||
|
|
||||||
def fetch_user_activities(user, reading_user, params \\ %{}) do
|
def fetch_user_activities(user, reading_user, params \\ %{}) do
|
||||||
params =
|
params =
|
||||||
params
|
params
|
||||||
|
@ -447,6 +482,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
||||||
|> restrict_recent(opts)
|
|> restrict_recent(opts)
|
||||||
|> restrict_blocked(opts)
|
|> restrict_blocked(opts)
|
||||||
|> restrict_media(opts)
|
|> restrict_media(opts)
|
||||||
|
|> restrict_visibility(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_activities(recipients, opts \\ %{}) do
|
def fetch_activities(recipients, opts \\ %{}) do
|
||||||
|
|
|
@ -220,6 +220,15 @@ def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
query = ActivityPub.fetch_activities_query([user.ap_id], %{visibility: "direct"})
|
||||||
|
activities = Repo.all(query)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> add_link_headers(:user_statuses, activities, user.ap_id)
|
||||||
|
|> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
|
||||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||||
true <- ActivityPub.visible_for_user?(activity, user) do
|
true <- ActivityPub.visible_for_user?(activity, user) do
|
||||||
|
|
|
@ -15,7 +15,7 @@ def connect(params, socket) do
|
||||||
with token when not is_nil(token) <- params["access_token"],
|
with token when not is_nil(token) <- params["access_token"],
|
||||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||||
%User{} = user <- Repo.get(User, user_id),
|
%User{} = user <- Repo.get(User, user_id),
|
||||||
stream when stream in ["public", "public:local", "user"] <- params["stream"] do
|
stream when stream in ["public", "public:local", "user", "direct"] <- params["stream"] do
|
||||||
socket =
|
socket =
|
||||||
socket
|
socket
|
||||||
|> assign(:topic, params["stream"])
|
|> assign(:topic, params["stream"])
|
||||||
|
|
|
@ -193,10 +193,18 @@ def get_visibility(object) do
|
||||||
cc = object["cc"] || []
|
cc = object["cc"] || []
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
public in to -> "public"
|
public in to ->
|
||||||
public in cc -> "unlisted"
|
"public"
|
||||||
Enum.any?(to, &String.contains?(&1, "/followers")) -> "private"
|
|
||||||
true -> "direct"
|
public in cc ->
|
||||||
|
"unlisted"
|
||||||
|
|
||||||
|
# this should use the sql for the object's activity
|
||||||
|
Enum.any?(to, &String.contains?(&1, "/followers")) ->
|
||||||
|
"private"
|
||||||
|
|
||||||
|
true ->
|
||||||
|
"direct"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -107,6 +107,8 @@ def user_fetcher(username) do
|
||||||
|
|
||||||
get("/timelines/home", MastodonAPIController, :home_timeline)
|
get("/timelines/home", MastodonAPIController, :home_timeline)
|
||||||
|
|
||||||
|
get("/timelines/direct", MastodonAPIController, :dm_timeline)
|
||||||
|
|
||||||
get("/favourites", MastodonAPIController, :favourites)
|
get("/favourites", MastodonAPIController, :favourites)
|
||||||
|
|
||||||
post("/statuses", MastodonAPIController, :post_status)
|
post("/statuses", MastodonAPIController, :post_status)
|
||||||
|
|
|
@ -46,6 +46,19 @@ def handle_cast(%{action: :ping}, topics) do
|
||||||
{:noreply, topics}
|
{:noreply, topics}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do
|
||||||
|
recipient_topics =
|
||||||
|
User.get_recipients_from_activity(item)
|
||||||
|
|> Enum.map(fn %{id: id} -> "direct:#{id}" end)
|
||||||
|
|
||||||
|
Enum.each(recipient_topics || [], fn user_topic ->
|
||||||
|
Logger.debug("Trying to push direct message to #{user_topic}\n\n")
|
||||||
|
push_to_socket(topics, user_topic, item)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:noreply, topics}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
||||||
topic = "user:#{item.user_id}"
|
topic = "user:#{item.user_id}"
|
||||||
|
|
||||||
|
@ -137,8 +150,8 @@ def push_to_socket(topics, topic, item) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp internal_topic("user", socket) do
|
defp internal_topic(topic, socket) when topic in ~w[user, direct] do
|
||||||
"user:#{socket.assigns[:user].id}"
|
"#{topic}:#{socket.assigns[:user].id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp internal_topic(topic, _), do: topic
|
defp internal_topic(topic, _), do: topic
|
||||||
|
|
|
@ -45,6 +45,33 @@ def note_factory do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def direct_note_factory do
|
||||||
|
user2 = insert(:user)
|
||||||
|
|
||||||
|
%Pleroma.Object{data: data} = note_factory()
|
||||||
|
%Pleroma.Object{data: Map.merge(data, %{"to" => [user2.ap_id]})}
|
||||||
|
end
|
||||||
|
|
||||||
|
def direct_note_activity_factory do
|
||||||
|
dm = insert(:direct_note)
|
||||||
|
|
||||||
|
data = %{
|
||||||
|
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => dm.data["actor"],
|
||||||
|
"to" => dm.data["to"],
|
||||||
|
"object" => dm.data,
|
||||||
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
||||||
|
"context" => dm.data["context"]
|
||||||
|
}
|
||||||
|
|
||||||
|
%Pleroma.Activity{
|
||||||
|
data: data,
|
||||||
|
actor: data["actor"],
|
||||||
|
recipients: data["to"]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def note_activity_factory do
|
def note_activity_factory do
|
||||||
note = insert(:note)
|
note = insert(:note)
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,61 @@ test "posting a sensitive status", %{conn: conn} do
|
||||||
assert Repo.get(Activity, id)
|
assert Repo.get(Activity, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "posting a direct status", %{conn: conn} do
|
||||||
|
user1 = insert(:user)
|
||||||
|
user2 = insert(:user)
|
||||||
|
content = "direct cofe @#{user2.nickname}"
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user1)
|
||||||
|
|> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
|
||||||
|
|
||||||
|
assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
|
||||||
|
assert activity = Repo.get(Activity, id)
|
||||||
|
assert activity.recipients == [user2.ap_id]
|
||||||
|
assert activity.data["to"] == [user2.ap_id]
|
||||||
|
assert activity.data["cc"] == []
|
||||||
|
end
|
||||||
|
|
||||||
|
test "direct timeline", %{conn: conn} do
|
||||||
|
user_one = insert(:user)
|
||||||
|
user_two = insert(:user)
|
||||||
|
|
||||||
|
{:ok, user_two} = User.follow(user_two, user_one)
|
||||||
|
|
||||||
|
{:ok, direct} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "direct"
|
||||||
|
})
|
||||||
|
|
||||||
|
{:ok, _follower_only} =
|
||||||
|
CommonAPI.post(user_one, %{
|
||||||
|
"status" => "Hi @#{user_two.nickname}!",
|
||||||
|
"visibility" => "private"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Only direct should be visible here
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/direct")
|
||||||
|
|
||||||
|
[status] = json_response(res_conn, 200)
|
||||||
|
|
||||||
|
assert %{"visibility" => "direct"} = status
|
||||||
|
assert status["url"] != direct.data["id"]
|
||||||
|
|
||||||
|
# Both should be visible here
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user_two)
|
||||||
|
|> get("api/v1/timelines/home")
|
||||||
|
|
||||||
|
[_s1, _s2] = json_response(res_conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
test "replying to a status", %{conn: conn} do
|
test "replying to a status", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue