diff options
Diffstat (limited to 'lib')
50 files changed, 844 insertions, 260 deletions
diff --git a/lib/mix/tasks/deactivate_user.ex b/lib/mix/tasks/deactivate_user.ex new file mode 100644 index 000000000..96b3db6e4 --- /dev/null +++ b/lib/mix/tasks/deactivate_user.ex @@ -0,0 +1,13 @@ +defmodule Mix.Tasks.DeactivateUser do + use Mix.Task + alias Pleroma.User + + @shortdoc "Toggle deactivation status for a user" + def run([nickname]) do + Mix.Task.run("app.start") + + with user <- User.get_by_nickname(nickname) do + User.deactivate(user) + end + end +end diff --git a/lib/mix/tasks/fix_ap_users.ex b/lib/mix/tasks/fix_ap_users.ex index 2523cdbcb..7e970850e 100644 --- a/lib/mix/tasks/fix_ap_users.ex +++ b/lib/mix/tasks/fix_ap_users.ex @@ -1,6 +1,5 @@ defmodule Mix.Tasks.FixApUsers do use Mix.Task - import Mix.Ecto import Ecto.Query alias Pleroma.{Repo, User} diff --git a/lib/mix/tasks/generate_password_reset.ex b/lib/mix/tasks/generate_password_reset.ex index e39134007..6bf640150 100644 --- a/lib/mix/tasks/generate_password_reset.ex +++ b/lib/mix/tasks/generate_password_reset.ex @@ -1,7 +1,6 @@ defmodule Mix.Tasks.GeneratePasswordReset do use Mix.Task - import Mix.Ecto - alias Pleroma.{Repo, User} + alias Pleroma.User @shortdoc "Generate password reset link for user" def run([nickname]) do diff --git a/lib/mix/tasks/register_user.ex b/lib/mix/tasks/register_user.ex index 4bec6b9d9..e74721c49 100644 --- a/lib/mix/tasks/register_user.ex +++ b/lib/mix/tasks/register_user.ex @@ -1,6 +1,5 @@ defmodule Mix.Tasks.RegisterUser do use Mix.Task - import Mix.Ecto alias Pleroma.{Repo, User} @shortdoc "Register user" diff --git a/lib/mix/tasks/rm_user.ex b/lib/mix/tasks/rm_user.ex index 6a698f360..27521b745 100644 --- a/lib/mix/tasks/rm_user.ex +++ b/lib/mix/tasks/rm_user.ex @@ -1,7 +1,6 @@ defmodule Mix.Tasks.RmUser do use Mix.Task - import Mix.Ecto - alias Pleroma.{User, Repo} + alias Pleroma.User @shortdoc "Permanently delete a user" def run([nickname]) do diff --git a/lib/mix/tasks/sample_psql.eex b/lib/mix/tasks/sample_psql.eex index 18e322efc..bc22f166c 100644 --- a/lib/mix/tasks/sample_psql.eex +++ b/lib/mix/tasks/sample_psql.eex @@ -6,3 +6,4 @@ ALTER DATABASE pleroma_dev OWNER TO pleroma; \c pleroma_dev; --Extensions made by ecto.migrate that need superuser access CREATE EXTENSION IF NOT EXISTS citext; +CREATE EXTENSION IF NOT EXISTS pg_trgm; diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 89826f515..a89728471 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Application do # for more information on OTP Applications def start(_type, _args) do import Supervisor.Spec + import Cachex.Spec # Define workers and child supervisors to be supervised children = @@ -23,6 +24,21 @@ defmodule Pleroma.Application do limit: 2500 ] ]), + worker( + Cachex, + [ + :idempotency_cache, + [ + expiration: + expiration( + default: :timer.seconds(6 * 60 * 60), + interval: :timer.seconds(60) + ), + limit: 2500 + ] + ], + id: :cachex_idem + ), worker(Pleroma.Web.Federator, []), worker(Pleroma.Gopher.Server, []), worker(Pleroma.Stats, []) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 456416fbd..53e2c204f 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -160,6 +160,7 @@ defmodule Pleroma.Formatter do links = Regex.scan(@link_regex, text) |> Enum.map(fn [url] -> {Ecto.UUID.generate(), url} end) + |> Enum.sort_by(fn {_, url} -> -String.length(url) end) uuid_text = links diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index 82e241f21..f6abcd4d0 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -65,12 +65,6 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do "#{type}#{name}\t#{selector}\t#{address}\t#{port}\r\n" end - def response("") do - info("Welcome to #{Keyword.get(@instance, :name, "Pleroma")}!") <> - link("Public Timeline", "/main/public") <> - link("Federated Timeline", "/main/all") <> ".\r\n" - end - def render_activities(activities) do activities |> Enum.reverse() @@ -93,6 +87,12 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do |> Enum.join("\r\n") end + def response("") do + info("Welcome to #{Keyword.get(@instance, :name, "Pleroma")}!") <> + link("Public Timeline", "/main/public") <> + link("Federated Timeline", "/main/all") <> ".\r\n" + end + def response("/main/public") do posts = ActivityPub.fetch_public_activities(%{"type" => ["Create"], "local_only" => true}) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index e26e49c8c..e0dcd9823 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -91,7 +91,8 @@ defmodule Pleroma.Notification do # TODO move to sql, too. def create_notification(%Activity{} = activity, %User{} = user) do - unless User.blocks?(user, %{ap_id: activity.data["actor"]}) do + unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or + user.ap_id == activity.data["actor"] do notification = %Notification{user_id: user.id, activity: activity} {:ok, notification} = Repo.insert(notification) Pleroma.Web.Streamer.stream("user", notification) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 558e151b0..ff2af4a6f 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -33,19 +33,15 @@ defmodule Pleroma.Object do else key = "object:#{ap_id}" - Cachex.get!( - :user_cache, - key, - fallback: fn _ -> - object = get_by_ap_id(ap_id) - - if object do - {:commit, object} - else - {:ignore, object} - end + Cachex.fetch!(:user_cache, key, fn _ -> + object = get_by_ap_id(ap_id) + + if object do + {:commit, object} + else + {:ignore, object} end - ) + end) end end diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex index 8b9ccdd2d..2d0e10cad 100644 --- a/lib/pleroma/plugs/http_signature.ex +++ b/lib/pleroma/plugs/http_signature.ex @@ -1,5 +1,6 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do alias Pleroma.Web.HTTPSignatures + alias Pleroma.Web.ActivityPub.Utils import Plug.Conn require Logger @@ -7,12 +8,12 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do options end - def call(%{assigns: %{valid_signature: true}} = conn, opts) do + def call(%{assigns: %{valid_signature: true}} = conn, _opts) do conn end - def call(conn, opts) do - user = conn.params["actor"] + def call(conn, _opts) do + user = Utils.normalize_actor(conn.params["actor"]) Logger.debug("Checking sig for #{user}") [signature | _] = get_req_header(conn, "signature") diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index 83b896a16..8478fe4ce 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -1,6 +1,6 @@ defmodule Pleroma.Stats do import Ecto.Query - alias Pleroma.{User, Repo, Activity} + alias Pleroma.{User, Repo} def start_link do agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 207674999..690cc7cf3 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -21,6 +21,7 @@ defmodule Pleroma.User do field(:local, :boolean, default: true) field(:info, :map, default: %{}) field(:follower_address, :string) + field(:search_distance, :float, virtual: true) has_many(:notifications, Notification) timestamps() @@ -222,9 +223,9 @@ defmodule Pleroma.User do def update_and_set_cache(changeset) do with {:ok, user} <- Repo.update(changeset) do - Cachex.set(:user_cache, "ap_id:#{user.ap_id}", user) - Cachex.set(:user_cache, "nickname:#{user.nickname}", user) - Cachex.set(:user_cache, "user_info:#{user.id}", user_info(user)) + Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user) + Cachex.put(:user_cache, "nickname:#{user.nickname}", user) + Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user)) {:ok, user} else e -> e @@ -238,12 +239,12 @@ defmodule Pleroma.User do def get_cached_by_ap_id(ap_id) do key = "ap_id:#{ap_id}" - Cachex.get!(:user_cache, key, fallback: fn _ -> get_by_ap_id(ap_id) end) + Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end) end def get_cached_by_nickname(nickname) do key = "nickname:#{nickname}" - Cachex.get!(:user_cache, key, fallback: fn _ -> get_or_fetch_by_nickname(nickname) end) + Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end) end def get_by_nickname(nickname) do @@ -259,7 +260,7 @@ defmodule Pleroma.User do def get_cached_user_info(user) do key = "user_info:#{user.id}" - Cachex.get!(:user_cache, key, fallback: fn _ -> user_info(user) end) + Cachex.fetch!(:user_cache, key, fn _ -> user_info(user) end) end def fetch_by_nickname(nickname) do @@ -399,16 +400,24 @@ defmodule Pleroma.User do User.get_or_fetch_by_nickname(query) end - q = + inner = from( u in User, - where: - fragment( - "(to_tsvector('english', ?) || to_tsvector('english', ?)) @@ plainto_tsquery('english', ?)", - u.nickname, - u.name, - ^query - ), + select_merge: %{ + search_distance: + fragment( + "? <-> (? || ?)", + ^query, + u.nickname, + u.name + ) + } + ) + + q = + from( + s in subquery(inner), + order_by: s.search_distance, limit: 20 ) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 3ddc009a1..8485a8009 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1,6 +1,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.{Activity, Repo, Object, Upload, User, Notification} - alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF} alias Pleroma.Web.WebFinger alias Pleroma.Web.Federator alias Pleroma.Web.OStatus @@ -11,16 +11,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do @httpoison Application.get_env(:pleroma, :httpoison) @instance Application.get_env(:pleroma, :instance) - @rewrite_policy Keyword.get(@instance, :rewrite_policy) def get_recipients(data) do (data["to"] || []) ++ (data["cc"] || []) end + defp check_actor_is_active(actor) do + if not is_nil(actor) do + with user <- User.get_cached_by_ap_id(actor), + nil <- user.info["deactivated"] do + :ok + else + _e -> :reject + end + else + :ok + end + end + def insert(map, local \\ true) when is_map(map) do with nil <- Activity.get_by_ap_id(map["id"]), map <- lazy_put_activity_defaults(map), - {:ok, map} <- @rewrite_policy.filter(map), + :ok <- check_actor_is_active(map["actor"]), + {:ok, map} <- MRF.filter(map), :ok <- insert_full_object(map) do {:ok, activity} = Repo.insert(%Activity{ @@ -66,7 +79,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ), {:ok, activity} <- insert(create_data, local), :ok <- maybe_federate(activity), - {:ok, actor} <- User.increase_note_count(actor) do + {:ok, _actor} <- User.increase_note_count(actor) do {:ok, activity} end end @@ -118,11 +131,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def unlike(%User{} = actor, %Object{} = object) do - with %Activity{} = activity <- get_existing_like(actor.ap_id, object), - {:ok, _activity} <- Repo.delete(activity), - {:ok, object} <- remove_like_from_object(activity, object) do - {:ok, object} + def unlike( + %User{} = actor, + %Object{} = object, + activity_id \\ nil, + local \\ true + ) do + with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object), + unlike_data <- make_unlike_data(actor, like_activity, activity_id), + {:ok, unlike_activity} <- insert(unlike_data, local), + {:ok, _activity} <- Repo.delete(like_activity), + {:ok, object} <- remove_like_from_object(like_activity, object), + :ok <- maybe_federate(unlike_activity) do + {:ok, unlike_activity, like_activity, object} else _e -> {:ok, object} end @@ -145,6 +166,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def unannounce( + %User{} = actor, + %Object{} = object, + activity_id \\ nil, + local \\ true + ) do + with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object), + unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id), + {:ok, unannounce_activity} <- insert(unannounce_data, local), + :ok <- maybe_federate(unannounce_activity), + {:ok, _activity} <- Repo.delete(announce_activity), + {:ok, object} <- remove_announce_from_object(announce_activity, object) do + {:ok, unannounce_activity, announce_activity, object} + else + _e -> {:ok, object} + end + end + def follow(follower, followed, activity_id \\ nil, local \\ true) do with data <- make_follow_data(follower, followed, activity_id), {:ok, activity} <- insert(data, local), @@ -153,12 +192,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def unfollow(follower, followed, local \\ true) do + def unfollow(follower, followed, activity_id \\ nil, local \\ true) do with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), - unfollow_data <- make_unfollow_data(follower, followed, follow_activity), + unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), {:ok, activity} <- insert(unfollow_data, local), - :ok, - maybe_federate(activity) do + :ok <- maybe_federate(activity) do {:ok, activity} end end @@ -177,7 +215,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)), {:ok, activity} <- insert(data, local), :ok <- maybe_federate(activity), - {:ok, actor} <- User.decrease_note_count(user) do + {:ok, _actor} <- User.decrease_note_count(user) do + {:ok, activity} + end + end + + def block(blocker, blocked, activity_id \\ nil, local \\ true) do + follow_activity = fetch_latest_follow(blocker, blocked) + + if follow_activity do + unfollow(blocker, blocked, nil, local) + end + + with block_data <- make_block_data(blocker, blocked, activity_id), + {:ok, activity} <- insert(block_data, local), + :ok <- maybe_federate(activity) do + {:ok, activity} + end + end + + def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do + with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked), + unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id), + {:ok, activity} <- insert(unblock_data, local), + :ok <- maybe_federate(activity) do {:ok, activity} end end @@ -212,15 +273,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Repo.all(query) end - # TODO: Make this work properly with unlisted. def fetch_public_activities(opts \\ %{}) do q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts) q + |> restrict_unlisted() |> Repo.all() |> Enum.reverse() end + def fetch_user_activities(user, reading_user, params \\ %{}) do + params = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("actor_id", user.ap_id) + |> Map.put("whole_db", true) + + recipients = + if reading_user do + ["https://www.w3.org/ns/activitystreams#Public"] ++ + [reading_user.ap_id | reading_user.following] + else + ["https://www.w3.org/ns/activitystreams#Public"] + end + + fetch_activities(recipients, params) + |> Enum.reverse() + end + defp restrict_since(query, %{"since_id" => since_id}) do from(activity in query, where: activity.id > ^since_id) end @@ -236,7 +316,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_tag(query, _), do: query - defp restrict_recipients(query, [], user), do: query + defp restrict_recipients(query, [], _user), do: query defp restrict_recipients(query, recipients, nil) do from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients)) @@ -323,6 +403,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_blocked(query, _), do: query + defp restrict_unlisted(query) do + from( + activity in query, + where: + fragment( + "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)", + activity.data, + ^["https://www.w3.org/ns/activitystreams#Public"] + ) + ) + end + def fetch_activities_query(recipients, opts \\ %{}) do base_query = from( @@ -372,6 +464,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "url" => [%{"href" => data["image"]["url"]}] } + data = Transmogrifier.maybe_fix_user_object(data) + user_data = %{ ap_id: data["id"], info: %{ @@ -400,7 +494,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def make_user_from_ap_id(ap_id) do - if user = User.get_by_ap_id(ap_id) do + if _user = User.get_by_ap_id(ap_id) do Transmogrifier.upgrade_user_from_ap_id(ap_id) else with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do @@ -496,7 +590,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do object = %Object{} -> {:ok, object} - e -> + _e -> Logger.info("Couldn't get object via AP, trying out OStatus fetching...") case OStatus.fetch_activity_from_url(id) do diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 12f61f5f0..c7d50893f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -1,7 +1,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do use Pleroma.Web, :controller - alias Pleroma.{User, Repo, Object, Activity} - alias Pleroma.Web.ActivityPub.{ObjectView, UserView, Transmogrifier} + alias Pleroma.{User, Object} + alias Pleroma.Web.ActivityPub.{ObjectView, UserView} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.Federator @@ -93,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do Logger.info("Signature not from author, relayed message, fetching from source") ActivityPub.fetch_object_from_id(params["object"]["id"]) else - Logger.info("Signature error") + Logger.info("Signature error - make sure you are forwarding the HTTP Host header!") Logger.info("Could not validate #{params["actor"]}") Logger.info(inspect(conn.req_headers)) end diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex new file mode 100644 index 000000000..0a4e2bf80 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -0,0 +1,24 @@ +defmodule Pleroma.Web.ActivityPub.MRF do + @callback filter(Map.t()) :: {:ok | :reject, Map.t()} + + def filter(object) do + get_policies() + |> Enum.reduce({:ok, object}, fn + policy, {:ok, object} -> + policy.filter(object) + + _, error -> + error + end) + end + + def get_policies() do + Application.get_env(:pleroma, :instance, []) + |> Keyword.get(:rewrite_policy, []) + |> get_policies() + end + + defp get_policies(policy) when is_atom(policy), do: [policy] + defp get_policies(policies) when is_list(policies), do: policies + defp get_policies(_), do: [] +end diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex index 4333bca28..811947943 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -1,6 +1,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do require Logger + @behaviour Pleroma.Web.ActivityPub.MRF + @impl true def filter(object) do Logger.info("REJECTING #{inspect(object)}") {:reject, object} diff --git a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex b/lib/pleroma/web/activity_pub/mrf/noop_policy.ex index 9dd3acb04..e26f60d26 100644 --- a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/noop_policy.ex @@ -1,4 +1,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do + @behaviour Pleroma.Web.ActivityPub.MRF + + @impl true def filter(object) do {:ok, object} end diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index ea1af0f4d..8d770387d 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -1,5 +1,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do alias Pleroma.User + @behaviour Pleroma.Web.ActivityPub.MRF @mrf_policy Application.get_env(:pleroma, :mrf_simple) @@ -17,9 +18,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do if actor_info.host in @media_removal do child_object = Map.delete(object["object"], "attachment") object = Map.put(object, "object", child_object) + {:ok, object} + else + {:ok, object} end - - {:ok, object} end @media_nsfw Keyword.get(@mrf_policy, :media_nsfw) @@ -32,9 +34,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do child_object = Map.put(child_object, "tags", tags) child_object = Map.put(child_object, "sensitive", true) object = Map.put(object, "object", child_object) + {:ok, object} + else + {:ok, object} end - - {:ok, object} end @ftl_removal Keyword.get(@mrf_policy, :federated_timeline_removal) @@ -43,24 +46,31 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do user = User.get_by_ap_id(object["actor"]) # flip to/cc relationship to make the post unlisted - if "https://www.w3.org/ns/activitystreams#Public" in object["to"] and - user.follower_address in object["cc"] do - to = - List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ - [user.follower_address] + object = + if "https://www.w3.org/ns/activitystreams#Public" in object["to"] and + user.follower_address in object["cc"] do + to = + List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ + [user.follower_address] - cc = - List.delete(object["cc"], user.follower_address) ++ - ["https://www.w3.org/ns/activitystreams#Public"] + cc = + List.delete(object["cc"], user.follower_address) ++ + ["https://www.w3.org/ns/activitystreams#Public"] - object = Map.put(object, "to", to) - object = Map.put(object, "cc", cc) - end - end + object + |> Map.put("to", to) + |> Map.put("cc", cc) + else + object + end - {:ok, object} + {:ok, object} + else + {:ok, object} + end end + @impl true def filter(object) do actor_info = URI.parse(object["actor"]) @@ -70,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do {:ok, object} <- check_ftl_removal(actor_info, object) do {:ok, object} else - e -> {:reject, nil} + _e -> {:reject, nil} end end end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 2871a2544..803445011 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -72,9 +72,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Enum.reduce(%{}, fn data, mapping -> name = data["name"] - if String.starts_with?(name, ":") do - name = name |> String.slice(1..-2) - end + name = + if String.starts_with?(name, ":") do + name |> String.slice(1..-2) + else + name + end mapping |> Map.put(name, data["icon"]["url"]) end) @@ -143,12 +146,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data + %{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = _data ) do with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), {:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), - {:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do + {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do {:ok, activity} else _e -> :error @@ -156,12 +159,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def handle_incoming( - %{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data + %{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = _data ) do with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), {:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), - {:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do + {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false) do {:ok, activity} else _e -> :error @@ -202,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # TODO: Make secure. def handle_incoming( - %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data + %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = _data ) do object_id = case object_id do @@ -210,19 +213,104 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do id -> id end - with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), + with %User{} = _actor <- User.get_or_fetch_by_ap_id(actor), {:ok, object} <- get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), {:ok, activity} <- ActivityPub.delete(object, false) do {:ok, activity} else + _e -> :error + end + end + + def handle_incoming( + %{ + "type" => "Undo", + "object" => %{"type" => "Announce", "object" => object_id}, + "actor" => actor, + "id" => id + } = _data + ) do + with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), + {:ok, object} <- + get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), + {:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do + {:ok, activity} + else + _e -> :error + end + end + + def handle_incoming( + %{ + "type" => "Undo", + "object" => %{"type" => "Follow", "object" => followed}, + "actor" => follower, + "id" => id + } = _data + ) do + with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), + %User{} = follower <- User.get_or_fetch_by_ap_id(follower), + {:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do + User.unfollow(follower, followed) + {:ok, activity} + else e -> :error end end + def handle_incoming( + %{ + "type" => "Undo", + "object" => %{"type" => "Block", "object" => blocked}, + "actor" => blocker, + "id" => id + } = _data + ) do + with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked), + %User{} = blocker <- User.get_or_fetch_by_ap_id(blocker), + {:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do + User.unblock(blocker, blocked) + {:ok, activity} + else + e -> :error + end + end + + def handle_incoming( + %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = data + ) do + with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked), + %User{} = blocker = User.get_or_fetch_by_ap_id(blocker), + {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do + User.unfollow(blocker, blocked) + User.block(blocker, blocked) + {:ok, activity} + else + e -> :error + end + end + + def handle_incoming( + %{ + "type" => "Undo", + "object" => %{"type" => "Like", "object" => object_id}, + "actor" => actor, + "id" => id + } = _data + ) do + with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), + {:ok, object} <- + get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), + {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do + {:ok, activity} + else + _e -> :error + end + end + # TODO # Accept - # Undo def handle_incoming(_), do: :error @@ -254,10 +342,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> set_reply_to_uri end - @doc - """ - internal -> Mastodon - """ + # @doc + # """ + # internal -> Mastodon + # """ def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do object = @@ -272,7 +360,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, data} end - def prepare_outgoing(%{"type" => type} = data) do + def prepare_outgoing(%{"type" => _type} = data) do data = data |> maybe_fix_object_url @@ -286,7 +374,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do case ActivityPub.fetch_object_from_id(data["object"]) do {:ok, relative_object} -> if relative_object.data["external_url"] do - data = + _data = data |> Map.put("object", relative_object.data["external_url"]) else @@ -474,4 +562,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Repo.delete_all(q) end end + + def maybe_fix_user_url(data) do + if is_map(data["url"]) do + Map.put(data, "url", data["url"]["href"]) + else + data + end + end + + def maybe_fix_user_object(data) do + data + |> maybe_fix_user_url + end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 7b2bf8fa7..831e13b7e 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -5,6 +5,22 @@ defmodule Pleroma.Web.ActivityPub.Utils do alias Ecto.{Changeset, UUID} import Ecto.Query + # Some implementations send the actor URI as the actor field, others send the entire actor object, + # so figure out what the actor's URI is based on what we have. + def normalize_actor(actor) do + cond do + is_binary(actor) -> + actor + + is_map(actor) -> + actor["id"] + end + end + + def normalize_params(params) do + Map.put(params, "actor", normalize_actor(params["actor"])) + end + def make_json_ld_header do %{ "@context" => [ @@ -226,8 +242,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do fragment( "? @> ?", activity.data, - ^%{type: "Follow", actor: follower_id, object: followed_id} + ^%{type: "Follow", object: followed_id} ), + where: activity.actor == ^follower_id, order_by: [desc: :id], limit: 1 ) @@ -238,6 +255,28 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Announce-related helpers @doc """ + Retruns an existing announce activity if the notice has already been announced + """ + def get_existing_announce(actor, %{data: %{"id" => id}}) do + query = + from( + activity in Activity, + where: activity.actor == ^actor, + # this is to use the index + where: + fragment( + "coalesce((?)->'object'->>'id', (?)->>'object') = ?", + activity.data, + activity.data, + ^id + ), + where: fragment("(?)->>'type' = 'Announce'", activity.data) + ) + + Repo.one(query) + end + + @doc """ Make announce activity data for the given actor and object """ def make_announce_data( @@ -257,21 +296,107 @@ defmodule Pleroma.Web.ActivityPub.Utils do if activity_id, do: Map.put(data, "id", activity_id), else: data end + @doc """ + Make unannounce activity data for the given actor and object + """ + def make_unannounce_data( + %User{ap_id: ap_id} = user, + %Activity{data: %{"context" => context}} = activity, + activity_id + ) do + data = %{ + "type" => "Undo", + "actor" => ap_id, + "object" => activity.data, + "to" => [user.follower_address, activity.data["actor"]], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "context" => context + } + + if activity_id, do: Map.put(data, "id", activity_id), else: data + end + + def make_unlike_data( + %User{ap_id: ap_id} = user, + %Activity{data: %{"context" => context}} = activity, + activity_id + ) do + data = %{ + "type" => "Undo", + "actor" => ap_id, + "object" => activity.data, + "to" => [user.follower_address, activity.data["actor"]], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "context" => context + } + + if activity_id, do: Map.put(data, "id", activity_id), else: data + end + def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do update_element_in_object("announcement", announcements, object) end end + def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do + with announcements <- (object.data["announcements"] || []) |> List.delete(actor) do + update_element_in_object("announcement", announcements, object) + end + end + #### Unfollow-related helpers - def make_unfollow_data(follower, followed, follow_activity) do - %{ + def make_unfollow_data(follower, followed, follow_activity, activity_id) do + data = %{ "type" => "Undo", "actor" => follower.ap_id, "to" => [followed.ap_id], - "object" => follow_activity.data["id"] + "object" => follow_activity.data + } + + if activity_id, do: Map.put(data, "id", activity_id), else: data + end + + #### Block-related helpers + def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do + query = + from( + activity in Activity, + where: + fragment( + "? @> ?", + activity.data, + ^%{type: "Block", object: blocked_id} + ), + where: activity.actor == ^blocker_id, + order_by: [desc: :id], + limit: 1 + ) + + Repo.one(query) + end + + def make_block_data(blocker, blocked, activity_id) do + data = %{ + "type" => "Block", + "actor" => blocker.ap_id, + "to" => [blocked.ap_id], + "object" => blocked.ap_id + } + + if activity_id, do: Map.put(data, "id", activity_id), else: data + end + + def make_unblock_data(blocker, blocked, block_activity, activity_id) do + data = %{ + "type" => "Undo", + "actor" => blocker.ap_id, + "to" => [blocked.ap_id], + "object" => block_activity.data } + + if activity_id, do: Map.put(data, "id", activity_id), else: data end #### Create-related helpers diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 92afd0872..ffd76b529 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -47,25 +47,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do |> Map.merge(Utils.make_json_ld_header()) end - def collection(collection, iri, page, total \\ nil) do - offset = (page - 1) * 10 - items = Enum.slice(collection, offset, 10) - items = Enum.map(items, fn user -> user.ap_id end) - total = total || length(collection) - - map = %{ - "id" => "#{iri}?page=#{page}", - "type" => "OrderedCollectionPage", - "partOf" => iri, - "totalItems" => length(collection), - "orderedItems" => items - } - - if offset < length(collection) do - Map.put(map, "next", "#{iri}?page=#{page + 1}") - end - end - def render("following.json", %{user: user, page: page}) do query = User.get_friends_query(user) query = from(user in query, select: [:ap_id]) @@ -123,9 +104,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do "limit" => "10" } - if max_qid != nil do - params = Map.put(params, "max_id", max_qid) - end + params = + if max_qid != nil do + Map.put(params, "max_id", max_qid) + else + params + end activities = ActivityPub.fetch_public_activities(params) min_id = Enum.at(activities, 0).id @@ -162,4 +146,23 @@ defmodule Pleroma.Web.ActivityPub.UserView do page |> Map.merge(Utils.make_json_ld_header()) end end + + def collection(collection, iri, page, total \\ nil) do + offset = (page - 1) * 10 + items = Enum.slice(collection, offset, 10) + items = Enum.map(items, fn user -> user.ap_id end) + total = total || length(collection) + + map = %{ + "id" => "#{iri}?page=#{page}", + "type" => "OrderedCollectionPage", + "partOf" => iri, + "totalItems" => total, + "orderedItems" => items + } + + if offset < total do + Map.put(map, "next", "#{iri}?page=#{page + 1}") + end + end end diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index fd8918a7d..21b22b409 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -1,7 +1,6 @@ defmodule Pleroma.Web.UserSocket do use Phoenix.Socket alias Pleroma.User - alias Comeonin.Pbkdf2 ## Channels # channel "room:*", Pleroma.Web.RoomChannel diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 2c4b591d4..8845419c2 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -1,5 +1,5 @@ defmodule Pleroma.Web.CommonAPI do - alias Pleroma.{Repo, Activity, Object, User} + alias Pleroma.{Repo, Activity, Object} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Formatter @@ -24,6 +24,16 @@ defmodule Pleroma.Web.CommonAPI do end end + def unrepeat(id_or_ap_id, user) do + with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), + object <- Object.get_by_ap_id(activity.data["object"]["id"]) do + ActivityPub.unannounce(user, object) + else + _ -> + {:error, "Could not unrepeat"} + end + end + def favorite(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), false <- activity.data["actor"] == user.ap_id, diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 3c092d524..9c9951371 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -1,7 +1,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do - alias Pleroma.{Repo, Object, Formatter, User, Activity} + alias Pleroma.{Repo, Object, Formatter, Activity} alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.User alias Calendar.Strftime + alias Comeonin.Pbkdf2 # This is a hack for twidere. def get_by_id_or_ap_id(id) do @@ -49,7 +51,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do {[user.follower_address | to], cc} end - def to_for_user_and_mentions(user, mentions, inReplyTo, "direct") do + def to_for_user_and_mentions(_user, mentions, inReplyTo, "direct") do mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end) if inReplyTo do @@ -69,7 +71,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do def make_context(%Activity{data: %{"context" => context}}), do: context def make_context(_), do: Utils.generate_context_id() - def maybe_add_attachments(text, attachments, _no_links = true), do: text + def maybe_add_attachments(text, _attachments, _no_links = true), do: text def maybe_add_attachments(text, attachments, _no_links) do add_attachments(text, attachments) @@ -131,7 +133,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do "context" => context, "attachment" => attachments, "actor" => actor, - "tag" => tags |> Enum.map(fn {_, tag} -> tag end) + "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq() } if inReplyTo do @@ -184,4 +186,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do String.slice(name, 0..30) <> "…" end end + + def confirm_current_password(user, password) do + with %User{local: true} = db_user <- Repo.get(User, user.id), + true <- Pbkdf2.checkpw(password, db_user.password_hash) do + {:ok, db_user} + else + _ -> {:error, "Invalid password."} + end + end end diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 8335add9c..8ca530031 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Web.{WebFinger, Websub} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.Utils require Logger @websub Application.get_env(:pleroma, :websub) @@ -14,6 +15,10 @@ defmodule Pleroma.Web.Federator do @federating Keyword.get(@instance, :federating) @max_jobs 20 + def init(args) do + {:ok, args} + end + def start_link do spawn(fn -> # 1 minute @@ -87,14 +92,16 @@ defmodule Pleroma.Web.Federator do def handle(:incoming_ap_doc, params) do Logger.info("Handling incoming AP activity") + params = Utils.normalize_params(params) + with {:ok, _user} <- ap_enabled_actor(params["actor"]), nil <- Activity.get_by_ap_id(params["id"]), - {:ok, activity} <- Transmogrifier.handle_incoming(params) do + {:ok, _activity} <- Transmogrifier.handle_incoming(params) do else %Activity{} -> Logger.info("Already had #{params["id"]}") - e -> + _e -> # Just drop those for now Logger.info("Unhandled activity") Logger.info(Poison.encode!(params, pretty: 2)) @@ -154,7 +161,7 @@ defmodule Pleroma.Web.Federator do end end - def handle_cast({:enqueue, type, payload, priority}, state) + def handle_cast({:enqueue, type, payload, _priority}, state) when type in [:incoming_doc, :incoming_ap_doc] do %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state i_queue = enqueue_sorted(i_queue, {type, payload}, 1) @@ -162,7 +169,7 @@ defmodule Pleroma.Web.Federator do {:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}} end - def handle_cast({:enqueue, type, payload, priority}, state) do + def handle_cast({:enqueue, type, payload, _priority}, state) do %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state o_queue = enqueue_sorted(o_queue, {type, payload}, 1) {o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue) diff --git a/lib/pleroma/web/http_signatures/http_signatures.ex b/lib/pleroma/web/http_signatures/http_signatures.ex index 0ea3b7554..4e0adbc1d 100644 --- a/lib/pleroma/web/http_signatures/http_signatures.ex +++ b/lib/pleroma/web/http_signatures/http_signatures.ex @@ -1,6 +1,7 @@ # https://tools.ietf.org/html/draft-cavage-http-signatures-08 defmodule Pleroma.Web.HTTPSignatures do alias Pleroma.User + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.ActivityPub require Logger @@ -31,21 +32,21 @@ defmodule Pleroma.Web.HTTPSignatures do def validate_conn(conn) do # TODO: How to get the right key and see if it is actually valid for that request. # For now, fetch the key for the actor. - with actor_id <- conn.params["actor"], + with actor_id <- Utils.normalize_actor(conn.params["actor"]), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do if validate_conn(conn, public_key) do true else Logger.debug("Could not validate, re-fetching user and trying one more time") # Fetch user anew and try one more time - with actor_id <- conn.params["actor"], + with actor_id <- Utils.normalize_actor(conn.params["actor"]), {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do validate_conn(conn, public_key) end end else - e -> + _e -> Logger.debug("Could not public key!") false end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 82ddb9a5d..460942f1a 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -112,7 +112,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})", email: Keyword.get(@instance, :email), urls: %{ - streaming_api: String.replace(Web.base_url(), ["http", "https"], "wss") + streaming_api: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws") }, stats: Stats.get_stats(), thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg", @@ -204,22 +204,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> render(StatusView, "index.json", %{activities: activities, for: user, as: :activity}) end - def user_statuses(%{assigns: %{user: user}} = conn, params) do - with %User{ap_id: ap_id} <- Repo.get(User, params["id"]) do - params = - params - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("actor_id", ap_id) - |> Map.put("whole_db", true) - - if params["pinned"] == "true" do - # Since Pleroma has no "pinned" posts feature, we'll just set an empty list here - activities = [] - else - activities = - ActivityPub.fetch_public_activities(params) - |> Enum.reverse() - end + def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do + with %User{} = user <- Repo.get(User, params["id"]) do + # Since Pleroma has no "pinned" posts feature, we'll just set an empty list here + activities = + if params["pinned"] == "true" do + [] + else + ActivityPub.fetch_user_activities(user, reading_user, params) + end conn |> add_link_headers(:user_statuses, activities, params["id"]) @@ -275,7 +268,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Map.put("in_reply_to_status_id", params["in_reply_to_id"]) |> Map.put("no_attachment_links", true) - {:ok, activity} = CommonAPI.post(user, params) + idempotency_key = + case get_req_header(conn, "idempotency-key") do + [key] -> key + _ -> Ecto.UUID.generate() + end + + {:ok, activity} = + Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end) + render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) end @@ -296,6 +297,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do + with {:ok, _, _, %{data: %{"id" => id}}} = CommonAPI.unrepeat(ap_id_or_id, user), + %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do + render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) + end + end + def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do @@ -304,7 +312,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), + with {:ok, _, _, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) end @@ -445,24 +453,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - # TODO: Clean up and unify def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with %User{} = followed <- Repo.get(User, id), - {:ok, follower, follow_activity} <- User.unfollow(follower, followed), - {:ok, _activity} <- - ActivityPub.insert(%{ - "type" => "Undo", - "actor" => follower.ap_id, - # get latest Follow for these users - "object" => follow_activity.data["id"] - }) do + {:ok, _activity} <- ActivityPub.unfollow(follower, followed), + {:ok, follower, _} <- User.unfollow(follower, followed) do render(conn, AccountView, "relationship.json", %{user: follower, target: followed}) end end def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do with %User{} = blocked <- Repo.get(User, id), - {:ok, blocker} <- User.block(blocker, blocked) do + {:ok, blocker} <- User.block(blocker, blocked), + {:ok, _activity} <- ActivityPub.block(blocker, blocked) do render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> @@ -474,7 +476,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do with %User{} = blocked <- Repo.get(User, id), - {:ok, blocker} <- User.unblock(blocker, blocked) do + {:ok, blocker} <- User.unblock(blocker, blocked), + {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do render(conn, AccountView, "relationship.json", %{user: blocker, target: blocked}) else {:error, message} -> diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index df360644a..5c6fd05f3 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -82,19 +82,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do - id = activity.data["object"]["inReplyTo"] - replied_to_activities[activity.data["object"]["inReplyTo"]] - end - - def get_reply_to(%{data: %{"object" => object}}, _) do - if object["inReplyTo"] && object["inReplyTo"] != "" do - Activity.get_create_activity_by_object_ap_id(object["inReplyTo"]) - else - nil - end - end - def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do user = User.get_cached_by_ap_id(activity.data["actor"]) @@ -164,19 +151,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def get_visibility(object) do - public = "https://www.w3.org/ns/activitystreams#Public" - to = object["to"] || [] - cc = object["cc"] || [] - - cond do - public in to -> "public" - public in cc -> "unlisted" - Enum.any?(to, &String.contains?(&1, "/followers")) -> "private" - true -> "direct" - end - end - def render("attachment.json", %{attachment: attachment}) do [%{"mediaType" => media_type, "href" => href} | _] = attachment["url"] @@ -199,4 +173,30 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do type: type } end + + def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do + _id = activity.data["object"]["inReplyTo"] + replied_to_activities[activity.data["object"]["inReplyTo"]] + end + + def get_reply_to(%{data: %{"object" => object}}, _) do + if object["inReplyTo"] && object["inReplyTo"] != "" do + Activity.get_create_activity_by_object_ap_id(object["inReplyTo"]) + else + nil + end + end + + def get_visibility(object) do + public = "https://www.w3.org/ns/activitystreams#Public" + to = object["to"] || [] + cc = object["cc"] || [] + + cond do + public in to -> "public" + public in cc -> "unlisted" + Enum.any?(to, &String.contains?(&1, "/followers")) -> "private" + true -> "direct" + end + end end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -0,0 +1 @@ + diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex new file mode 100644 index 000000000..aec77168a --- /dev/null +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -0,0 +1,62 @@ +defmodule Pleroma.Web.Nodeinfo.NodeinfoController do + use Pleroma.Web, :controller + + alias Pleroma.Stats + alias Pleroma.Web + + @instance Application.get_env(:pleroma, :instance) + + def schemas(conn, _params) do + response = %{ + links: [ + %{ + rel: "http://nodeinfo.diaspora.software/ns/schema/2.0", + href: Web.base_url() <> "/nodeinfo/2.0.json" + } + ] + } + + json(conn, response) + end + + # Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json + def nodeinfo(conn, %{"version" => "2.0"}) do + stats = Stats.get_stats() + + response = %{ + version: "2.0", + software: %{ + name: "pleroma", + version: Keyword.get(@instance, :version) + }, + protocols: ["ostatus", "activitypub"], + services: %{ + inbound: [], + outbound: [] + }, + openRegistrations: Keyword.get(@instance, :registrations_open), + usage: %{ + users: %{ + total: stats.user_count || 0 + }, + localPosts: stats.status_count || 0 + }, + metadata: %{ + nodeName: Keyword.get(@instance, :name) + } + } + + conn + |> put_resp_header( + "content-type", + "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" + ) + |> json(response) + end + + def nodeinfo(conn, _) do + conn + |> put_status(404) + |> json(%{error: "Nodeinfo schema version not handled"}) + end +end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index 94f44c9f2..23e8eb7b1 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.OAuth.Authorization do field(:valid_until, :naive_datetime) field(:used, :boolean, default: false) belongs_to(:user, Pleroma.User) - belongs_to(:app, Pleroma.App) + belongs_to(:app, App) timestamps() end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 65abd78c8..343fc0c45 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.OAuth.Token do field(:refresh_token, :string) field(:valid_until, :naive_datetime) belongs_to(:user, Pleroma.User) - belongs_to(:app, Pleroma.App) + belongs_to(:app, App) timestamps() end diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 921a89bd0..4179d86c9 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -1,7 +1,6 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do alias Pleroma.{Activity, User, Object} alias Pleroma.Web.OStatus.UserRepresenter - alias Pleroma.Formatter require Logger defp get_href(id) do @@ -233,16 +232,21 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do end # Only undos of follow for now. Will need to get redone once there are more - def to_simple_form(%{data: %{"type" => "Undo"}} = activity, user, with_author) do + def to_simple_form( + %{data: %{"type" => "Undo", "object" => %{"type" => "Follow"} = follow_activity}} = + activity, + user, + with_author + ) do h = fn str -> [to_charlist(str)] end updated_at = activity.data["published"] inserted_at = activity.data["published"] author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: [] - follow_activity = Activity.get_by_ap_id(activity.data["object"]) mentions = (activity.recipients || []) |> get_mentions + follow_activity = Activity.get_by_ap_id(follow_activity["id"]) [ {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']}, diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex index bd6e92238..0d4080291 100644 --- a/lib/pleroma/web/ostatus/handlers/note_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex @@ -1,7 +1,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do require Logger alias Pleroma.Web.{XML, OStatus} - alias Pleroma.{Object, User, Activity} + alias Pleroma.{Object, Activity} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI diff --git a/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex b/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex new file mode 100644 index 000000000..a115bf4c8 --- /dev/null +++ b/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex @@ -0,0 +1,17 @@ +defmodule Pleroma.Web.OStatus.UnfollowHandler do + alias Pleroma.Web.{XML, OStatus} + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.User + + def handle(entry, doc) do + with {:ok, actor} <- OStatus.find_make_or_update_user(doc), + id when not is_nil(id) <- XML.string_from_xpath("/entry/id", entry), + followed_uri when not is_nil(followed_uri) <- + XML.string_from_xpath("/entry/activity:object/id", entry), + {:ok, followed} <- OStatus.find_or_make_user(followed_uri), + {:ok, activity} <- ActivityPub.unfollow(actor, followed, id, false) do + User.unfollow(actor, followed) + {:ok, activity} + end + end +end diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 5c4a1fd69..f0ff0624f 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Web.OStatus do alias Pleroma.{Repo, User, Web, Object, Activity} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.{WebFinger, Websub} - alias Pleroma.Web.OStatus.{FollowHandler, NoteHandler, DeleteHandler} + alias Pleroma.Web.OStatus.{FollowHandler, UnfollowHandler, NoteHandler, DeleteHandler} alias Pleroma.Web.ActivityPub.Transmogrifier def feed_path(user) do @@ -47,6 +47,9 @@ defmodule Pleroma.Web.OStatus do 'http://activitystrea.ms/schema/1.0/follow' -> with {:ok, activity} <- FollowHandler.handle(entry, doc), do: activity + 'http://activitystrea.ms/schema/1.0/unfollow' -> + with {:ok, activity} <- UnfollowHandler.handle(entry, doc), do: activity + 'http://activitystrea.ms/schema/1.0/share' -> with {:ok, activity, retweeted_activity} <- handle_share(entry, doc), do: [activity, retweeted_activity] diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index a02f55fe6..f39ebaf2b 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Web.XML alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPub - import Ecto.Query def feed_redirect(conn, %{"nickname" => nickname} = params) do user = User.get_cached_by_nickname(nickname) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c58f77817..726275158 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -73,6 +73,8 @@ defmodule Pleroma.Web.Router do scope "/api/pleroma", Pleroma.Web.TwitterAPI do pipe_through(:authenticated_api) post("/follow_import", UtilController, :follow_import) + post("/change_password", UtilController, :change_password) + post("/delete_account", UtilController, :delete_account) end scope "/oauth", Pleroma.Web.OAuth do @@ -111,6 +113,7 @@ defmodule Pleroma.Web.Router do delete("/statuses/:id", MastodonAPIController, :delete_status) post("/statuses/:id/reblog", MastodonAPIController, :reblog_status) + post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status) post("/statuses/:id/favourite", MastodonAPIController, :fav_status) post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status) @@ -304,6 +307,11 @@ defmodule Pleroma.Web.Router do get("/host-meta", WebFinger.WebFingerController, :host_meta) get("/webfinger", WebFinger.WebFingerController, :webfinger) + get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas) + end + + scope "/nodeinfo", Pleroma.Web do + get("/:version", Nodeinfo.NodeinfoController, :nodeinfo) end end diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 10542fd00..562ec3d9c 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -187,13 +187,14 @@ defmodule Pleroma.Web.Salmon do def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster) when type in @supported_activities do - feed = - ActivityRepresenter.to_simple_form(activity, user, true) - |> ActivityRepresenter.wrap_with_entry() - |> :xmerl.export_simple(:xmerl_xml) - |> to_string + feed = ActivityRepresenter.to_simple_form(activity, user, true) if feed do + feed = + ActivityRepresenter.wrap_with_entry(feed) + |> :xmerl.export_simple(:xmerl_xml) + |> to_string + {:ok, private, _} = keys_from_pem(keys) {:ok, feed} = encode(private, feed) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 10670e71f..33041ec12 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -3,6 +3,10 @@ defmodule Pleroma.Web.Streamer do require Logger alias Pleroma.{User, Notification} + def init(args) do + {:ok, args} + end + def start_link do spawn(fn -> # 30 seconds @@ -110,20 +114,26 @@ defmodule Pleroma.Web.Streamer do def push_to_socket(topics, topic, item) do Enum.each(topics[topic] || [], fn socket -> - json = - %{ - event: "update", - payload: - Pleroma.Web.MastodonAPI.StatusView.render( - "status.json", - activity: item, - for: socket.assigns[:user] - ) - |> Jason.encode!() - } - |> Jason.encode!() - - send(socket.transport_pid, {:text, json}) + # Get the current user so we have up-to-date blocks etc. + user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) + blocks = user.info["blocks"] || [] + + unless item.actor in blocks do + json = + %{ + event: "update", + payload: + Pleroma.Web.MastodonAPI.StatusView.render( + "status.json", + activity: item, + for: user + ) + |> Jason.encode!() + } + |> Jason.encode!() + + send(socket.transport_pid, {:text, json}) + end end) end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index c7b1a5b95..cc5146566 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Web alias Pleroma.Web.OStatus alias Pleroma.Web.WebFinger + alias Pleroma.Web.CommonAPI alias Comeonin.Pbkdf2 alias Pleroma.Formatter alias Pleroma.Web.ActivityPub.ActivityPub @@ -92,7 +93,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do with %User{} = user <- User.get_cached_by_nickname(username), true <- Pbkdf2.checkpw(password, user.password_hash), - %User{} = followed <- Repo.get(User, id), + %User{} = _followed <- Repo.get(User, id), {:ok, follower} <- User.follow(user, followee), {:ok, _activity} <- ActivityPub.follow(follower, followee) do conn @@ -195,4 +196,38 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do json(conn, "job started") end + + def change_password(%{assigns: %{user: user}} = conn, params) do + case CommonAPI.Utils.confirm_current_password(user, params["password"]) do + {:ok, user} -> + with {:ok, _user} <- + User.reset_password(user, %{ + password: params["new_password"], + password_confirmation: params["new_password_confirmation"] + }) do + json(conn, %{status: "success"}) + else + {:error, changeset} -> + {_, {error, _}} = Enum.at(changeset.errors, 0) + json(conn, %{error: "New password #{error}."}) + + _ -> + json(conn, %{error: "Unable to change password."}) + end + + {:error, msg} -> + json(conn, %{error: msg}) + end + end + + def delete_account(%{assigns: %{user: user}} = conn, params) do + case CommonAPI.Utils.confirm_current_password(user, params["password"]) do + {:ok, user} -> + Task.start(fn -> User.delete(user) end) + json(conn, %{status: "success"}) + + {:error, msg} -> + json(conn, %{error: msg}) + end + end end diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex index 9a4954de8..57837205e 100644 --- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex +++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex @@ -99,7 +99,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do ) do created_at = created_at |> Utils.date_to_asctime() - text = "#{user.nickname} undid the action at #{undid_activity}" + text = "#{user.nickname} undid the action at #{undid_activity["id"]}" %{ "id" => activity.id, @@ -197,7 +197,8 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do "external_url" => object["external_url"] || object["id"], "tags" => tags, "activity_type" => "post", - "possibly_sensitive" => possibly_sensitive + "possibly_sensitive" => possibly_sensitive, + "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object) } end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index c12cd7f8a..3ccdaed6f 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -1,7 +1,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do alias Pleroma.{User, Activity, Repo, Object} alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.{OStatus, CommonAPI} import Ecto.Query @@ -12,6 +11,18 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do CommonAPI.post(user, data) end + def delete(%User{} = user, id) do + # TwitterAPI does not have an "unretweet" endpoint; instead this is done + # via the "destroy" endpoint. Therefore, we need to handle + # when the status to "delete" is actually an Announce (repeat) object. + with %Activity{data: %{"type" => type}} <- Repo.get(Activity, id) do + case type do + "Announce" -> unrepeat(user, id) + _ -> CommonAPI.delete(id, user) + end + end + end + def follow(%User{} = follower, params) do with {:ok, %User{} = followed} <- get_user(params), {:ok, follower} <- User.follow(follower, followed), @@ -25,14 +36,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def unfollow(%User{} = follower, params) do with {:ok, %User{} = unfollowed} <- get_user(params), {:ok, follower, follow_activity} <- User.unfollow(follower, unfollowed), - {:ok, _activity} <- - ActivityPub.insert(%{ - "type" => "Undo", - "actor" => follower.ap_id, - # get latest Follow for these users - "object" => follow_activity.data["id"], - "published" => make_date() - }) do + {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do {:ok, follower, unfollowed} else err -> err @@ -41,7 +45,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def block(%User{} = blocker, params) do with {:ok, %User{} = blocked} <- get_user(params), - {:ok, blocker} <- User.block(blocker, blocked) do + {:ok, blocker} <- User.block(blocker, blocked), + {:ok, _activity} <- ActivityPub.block(blocker, blocked) do {:ok, blocker, blocked} else err -> err @@ -50,7 +55,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def unblock(%User{} = blocker, params) do with {:ok, %User{} = blocked} <- get_user(params), - {:ok, blocker} <- User.unblock(blocker, blocked) do + {:ok, blocker} <- User.unblock(blocker, blocked), + {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do {:ok, blocker, blocked} else err -> err @@ -64,15 +70,21 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end end + defp unrepeat(%User{} = user, ap_id_or_id) do + with {:ok, _unannounce, activity, _object} <- CommonAPI.unrepeat(ap_id_or_id, user) do + {:ok, activity} + end + end + def fav(%User{} = user, ap_id_or_id) do - with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), + with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do {:ok, activity} end end def unfav(%User{} = user, ap_id_or_id) do - with {:ok, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), + with {:ok, _unfav, _fav, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do {:ok, activity} end @@ -184,7 +196,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do defp parse_int(_, default), do: default - def search(user, %{"q" => query} = params) do + def search(_user, %{"q" => query} = params) do limit = parse_int(params["rpp"], 20) page = parse_int(params["page"], 1) offset = (page - 1) * limit @@ -206,7 +218,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do order_by: [desc: :inserted_at] ) - activities = Repo.all(q) + _activities = Repo.all(q) end defp make_date do diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 6cf8682b8..dd1dc241d 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -96,13 +96,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def user_timeline(%{assigns: %{user: user}} = conn, params) do case TwitterAPI.get_user(user, params) do {:ok, target_user} -> - params = - params - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("actor_id", target_user.ap_id) - |> Map.put("whole_db", true) - - activities = ActivityPub.fetch_public_activities(params) + activities = ActivityPub.fetch_user_activities(target_user, user, params) conn |> render(ActivityView, "index.json", %{activities: activities, for: user}) @@ -157,8 +151,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, delete} <- CommonAPI.delete(id, user) do - render(conn, ActivityView, "activity.json", %{activity: delete, for: user}) + with {:ok, activity} <- TwitterAPI.delete(user, id) do + render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) end end @@ -347,7 +341,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def update_profile(%{assigns: %{user: user}} = conn, params) do params = if bio = params["description"] do - Map.put(params, "bio", bio) + bio_brs = Regex.replace(~r/\r?\n/, bio, "<br>") + Map.put(params, "bio", bio_brs) else params end diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index 4e4433aed..62ce3b7b5 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do end defp collect_context_ids(activities) do - contexts = + _contexts = activities |> Enum.reject(& &1.data["context_id"]) |> Enum.map(fn %{data: data} -> @@ -262,7 +262,8 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "external_url" => object["external_url"] || object["id"], "tags" => tags, "activity_type" => "post", - "possibly_sensitive" => possibly_sensitive + "possibly_sensitive" => possibly_sensitive, + "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object) } end end diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex index f41edea0b..9eeb3afdc 100644 --- a/lib/pleroma/web/twitter_api/views/notification_view.ex +++ b/lib/pleroma/web/twitter_api/views/notification_view.ex @@ -2,7 +2,6 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do use Pleroma.Web, :view alias Pleroma.{Notification, User} alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.MediaProxy alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.TwitterAPI.ActivityView diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index dc9ad2014..9c6f1cb68 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -1,7 +1,7 @@ defmodule Pleroma.Web.WebFinger do @httpoison Application.get_env(:pleroma, :httpoison) - alias Pleroma.{Repo, User, XmlBuilder} + alias Pleroma.{User, XmlBuilder} alias Pleroma.Web alias Pleroma.Web.{XML, Salmon, OStatus} require Jason @@ -87,6 +87,11 @@ defmodule Pleroma.Web.WebFinger do }, %{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id}, %{ + "rel" => "self", + "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", + "href" => user.ap_id + }, + %{ "rel" => "http://ostatus.org/schema/1.0/subscribe", "template" => OStatus.remote_follow_path() } @@ -183,6 +188,9 @@ defmodule Pleroma.Web.WebFinger do {"application/activity+json", "self"} -> Map.put(data, "ap_id", link["href"]) + {"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "self"} -> + Map.put(data, "ap_id", link["href"]) + {_, "magic-public-key"} -> "data:application/magic-public-key," <> magic_key = link["href"] Map.put(data, "magic_key", magic_key) @@ -206,7 +214,7 @@ defmodule Pleroma.Web.WebFinger do end def get_template_from_xml(body) do - xpath = "//Link[@rel='lrdd' and @type='application/xrd+xml']/@template" + xpath = "//Link[@rel='lrdd']/@template" with doc when doc != :error <- XML.parse_document(body), template when template != nil <- XML.string_from_xpath(xpath, doc) do @@ -239,13 +247,14 @@ defmodule Pleroma.Web.WebFinger do URI.parse(account).host end - case find_lrdd_template(domain) do - {:ok, template} -> - address = String.replace(template, "{uri}", URI.encode(account)) + address = + case find_lrdd_template(domain) do + {:ok, template} -> + String.replace(template, "{uri}", URI.encode(account)) - _ -> - address = "http://#{domain}/.well-known/webfinger?resource=acct:#{account}" - end + _ -> + "http://#{domain}/.well-known/webfinger?resource=acct:#{account}" + end with response <- @httpoison.get( diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml/xml.ex index 8b609f695..36430a3fa 100644 --- a/lib/pleroma/web/xml/xml.ex +++ b/lib/pleroma/web/xml/xml.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.XML do if res == "", do: nil, else: res catch - e -> + _e -> Logger.debug("Couldn't find xpath #{xpath} in XML doc") nil end |