forked from AkkomaGang/akkoma
Merge branch 'csaurus/pleroma-feature/mstdn-direct-api' into develop
This commit is contained in:
commit
46cc6fab07
8 changed files with 158 additions and 8 deletions
|
@ -53,15 +53,24 @@ def insert(map, local \\ true) when is_map(map) do
|
|||
end
|
||||
|
||||
def stream_out(activity) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
if activity.data["type"] in ["Create", "Announce"] do
|
||||
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)
|
||||
|
||||
if activity.local do
|
||||
Pleroma.Web.Streamer.stream("public:local", activity)
|
||||
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
|
||||
|
@ -293,6 +302,32 @@ def fetch_public_activities(opts \\ %{}) do
|
|||
|> Enum.reverse()
|
||||
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
|
||||
params =
|
||||
params
|
||||
|
@ -447,6 +482,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|
|||
|> restrict_recent(opts)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_media(opts)
|
||||
|> restrict_visibility(opts)
|
||||
end
|
||||
|
||||
def fetch_activities(recipients, opts \\ %{}) do
|
||||
|
|
|
@ -220,6 +220,15 @@ def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
|||
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
|
||||
with %Activity{} = activity <- Repo.get(Activity, id),
|
||||
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"],
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%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
|
||||
|> assign(:topic, params["stream"])
|
||||
|
|
|
@ -193,10 +193,18 @@ def get_visibility(object) do
|
|||
cc = object["cc"] || []
|
||||
|
||||
cond do
|
||||
public in to -> "public"
|
||||
public in cc -> "unlisted"
|
||||
Enum.any?(to, &String.contains?(&1, "/followers")) -> "private"
|
||||
true -> "direct"
|
||||
public in to ->
|
||||
"public"
|
||||
|
||||
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
|
||||
|
|
|
@ -107,6 +107,8 @@ def user_fetcher(username) do
|
|||
|
||||
get("/timelines/home", MastodonAPIController, :home_timeline)
|
||||
|
||||
get("/timelines/direct", MastodonAPIController, :dm_timeline)
|
||||
|
||||
get("/favourites", MastodonAPIController, :favourites)
|
||||
|
||||
post("/statuses", MastodonAPIController, :post_status)
|
||||
|
|
|
@ -46,6 +46,19 @@ def handle_cast(%{action: :ping}, topics) do
|
|||
{:noreply, topics}
|
||||
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
|
||||
topic = "user:#{item.user_id}"
|
||||
|
||||
|
@ -137,8 +150,8 @@ def push_to_socket(topics, topic, item) do
|
|||
end)
|
||||
end
|
||||
|
||||
defp internal_topic("user", socket) do
|
||||
"user:#{socket.assigns[:user].id}"
|
||||
defp internal_topic(topic, socket) when topic in ~w[user, direct] do
|
||||
"#{topic}:#{socket.assigns[:user].id}"
|
||||
end
|
||||
|
||||
defp internal_topic(topic, _), do: topic
|
||||
|
|
|
@ -45,6 +45,33 @@ def note_factory do
|
|||
}
|
||||
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
|
||||
note = insert(:note)
|
||||
|
||||
|
|
|
@ -124,6 +124,61 @@ test "posting a sensitive status", %{conn: conn} do
|
|||
assert Repo.get(Activity, id)
|
||||
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
|
||||
user = insert(:user)
|
||||
|
||||
|
|
Loading…
Reference in a new issue