diff options
author | Roman Chvanikov <chvanikoff@gmail.com> | 2019-04-20 19:43:06 +0700 |
---|---|---|
committer | Roman Chvanikov <chvanikoff@gmail.com> | 2019-04-20 19:43:06 +0700 |
commit | 60f213bb1a6b1627cd8f6bf2903a42c3f3827f1a (patch) | |
tree | 297f8ad25104db162b4431f887bb23b3e22255e2 /lib | |
parent | 05cdb2f2389376081973d96b32e876d2a032d1f1 (diff) | |
parent | 6ca82c18a738205483dbbef2fd4de662d70227fb (diff) | |
download | pleroma-60f213bb1a6b1627cd8f6bf2903a42c3f3827f1a.tar.gz |
Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/digest-email
Diffstat (limited to 'lib')
22 files changed, 422 insertions, 323 deletions
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex new file mode 100644 index 000000000..ab9a3a7ff --- /dev/null +++ b/lib/mix/tasks/pleroma/database.ex @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.Database do + alias Mix.Tasks.Pleroma.Common + require Logger + use Mix.Task + + @shortdoc "A collection of database related tasks" + @moduledoc """ + A collection of database related tasks + + ## Replace embedded objects with their references + + Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration. + + mix pleroma.database remove_embedded_objects + + Options: + - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references + """ + def run(["remove_embedded_objects" | args]) do + {options, [], []} = + OptionParser.parse( + args, + strict: [ + vacuum: :boolean + ] + ) + + Common.start_pleroma() + Logger.info("Removing embedded objects") + + Pleroma.Repo.query!( + "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;", + [], + timeout: :infinity + ) + + if Keyword.get(options, :vacuum) do + Logger.info("Runnning VACUUM FULL") + + Pleroma.Repo.query!( + "vacuum full;", + [], + timeout: :infinity + ) + end + end +end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index e6507e5ca..4a2ded518 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Activity do alias Pleroma.Object alias Pleroma.Repo + import Ecto.Changeset import Ecto.Query @type t :: %__MODULE__{} @@ -79,6 +80,13 @@ defmodule Pleroma.Activity do ) end + def change(struct, params \\ %{}) do + struct + |> cast(params, [:data]) + |> validate_required([:data]) + |> unique_constraint(:ap_id, name: :activities_unique_apid_index) + end + def get_by_ap_id_with_object(ap_id) do Repo.one( from( @@ -196,21 +204,27 @@ defmodule Pleroma.Activity do def create_by_object_ap_id_with_object(_), do: nil - def get_create_by_object_ap_id_with_object(ap_id) do + def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do ap_id |> create_by_object_ap_id_with_object() |> Repo.one() end - def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"]) - def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id) - def normalize(_), do: nil + def get_create_by_object_ap_id_with_object(_), do: nil - def get_in_reply_to_activity(%Activity{data: %{"object" => %{"inReplyTo" => ap_id}}}) do - get_create_by_object_ap_id(ap_id) + defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do + get_create_by_object_ap_id_with_object(ap_id) end - def get_in_reply_to_activity(_), do: nil + defp get_in_reply_to_activity_from_object(_), do: nil + + def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do + get_in_reply_to_activity_from_object(Object.normalize(object)) + end + + def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"]) + def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id) + def normalize(_), do: nil def delete_by_ap_id(id) when is_binary(id) do by_object_ap_id(id) @@ -218,6 +232,7 @@ defmodule Pleroma.Activity do |> Repo.delete_all() |> elem(1) |> Enum.find(fn + %{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id _ -> nil end) @@ -245,54 +260,4 @@ defmodule Pleroma.Activity do |> where([s], s.actor == ^actor) |> Repo.all() end - - def increase_replies_count(nil), do: nil - - def increase_replies_count(object_ap_id) do - from(a in create_by_object_ap_id(object_ap_id), - update: [ - set: [ - data: - fragment( - """ - jsonb_set(?, '{object, repliesCount}', - (coalesce((?->'object'->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true) - """, - a.data, - a.data - ) - ] - ] - ) - |> Repo.update_all([]) - |> case do - {1, [activity]} -> activity - _ -> {:error, "Not found"} - end - end - - def decrease_replies_count(nil), do: nil - - def decrease_replies_count(object_ap_id) do - from(a in create_by_object_ap_id(object_ap_id), - update: [ - set: [ - data: - fragment( - """ - jsonb_set(?, '{object, repliesCount}', - (greatest(0, (?->'object'->>'repliesCount')::int - 1))::varchar::jsonb, true) - """, - a.data, - a.data - ) - ] - ] - ) - |> Repo.update_all([]) - |> case do - {1, [activity]} -> activity - _ -> {:error, "Not found"} - end - end end diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index 6a56a6f67..2ebc5d5f7 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -38,6 +38,7 @@ end defmodule Pleroma.Gopher.Server.ProtocolHandler do alias Pleroma.Activity alias Pleroma.HTML + alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility @@ -75,14 +76,14 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do |> Enum.map(fn activity -> user = User.get_cached_by_ap_id(activity.data["actor"]) - object = activity.data["object"] + object = Object.normalize(activity.data["object"]) like_count = object["like_count"] || 0 announcement_count = object["announcement_count"] || 0 link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <> info("#{like_count} likes, #{announcement_count} repeats") <> "i\tfake\t(NULL)\t0\r\n" <> - info(HTML.strip_tags(String.replace(activity.data["object"]["content"], "<br>", "\r"))) + info(HTML.strip_tags(String.replace(object["content"], "<br>", "\r"))) end) |> Enum.join("i\tfake\t(NULL)\t0\r\n") end diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 7f1dbe28c..4b42d8c9b 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -32,7 +32,8 @@ defmodule Pleroma.HTML do key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}" Cachex.fetch!(:scrubber_cache, key, fn _key -> - ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false) + object = Pleroma.Object.normalize(activity) + ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false) end) end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 013d62157..740d687a3 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Object do alias Pleroma.Activity alias Pleroma.Object + alias Pleroma.Object.Fetcher alias Pleroma.ObjectTombstone alias Pleroma.Repo alias Pleroma.User @@ -40,41 +41,44 @@ defmodule Pleroma.Object do Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id))) end + def normalize(_, fetch_remote \\ true) # If we pass an Activity to Object.normalize(), we can try to use the preloaded object. # Use this whenever possible, especially when walking graphs in an O(N) loop! - def normalize(%Activity{object: %Object{} = object}), do: object + def normalize(%Object{} = object, _), do: object + def normalize(%Activity{object: %Object{} = object}, _), do: object # A hack for fake activities - def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}) do + def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do %Object{id: "pleroma:fake_object_id", data: data} end # Catch and log Object.normalize() calls where the Activity's child object is not # preloaded. - def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do + def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do Logger.debug( "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" ) Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") - normalize(ap_id) + normalize(ap_id, fetch_remote) end - def normalize(%Activity{data: %{"object" => ap_id}}) do + def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do Logger.debug( "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" ) Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") - normalize(ap_id) + normalize(ap_id, fetch_remote) end # Old way, try fetching the object through cache. - def normalize(%{"id" => ap_id}), do: normalize(ap_id) - def normalize(ap_id) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) - def normalize(_), do: nil + def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote) + def normalize(ap_id, false) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) + def normalize(ap_id, true) when is_binary(ap_id), do: Fetcher.fetch_object_from_id!(ap_id) + def normalize(_, _), do: nil # Owned objects can only be mutated by their owner def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}), diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex new file mode 100644 index 000000000..25bd911fb --- /dev/null +++ b/lib/pleroma/object/containment.ex @@ -0,0 +1,61 @@ +defmodule Pleroma.Object.Containment do + @moduledoc """ + # Object Containment + + This module contains some useful functions for containing objects to specific + origins and determining those origins. They previously lived in the + ActivityPub `Transmogrifier` module. + + Object containment is an important step in validating remote objects to prevent + spoofing, therefore removal of object containment functions is NOT recommended. + """ + def get_actor(%{"actor" => actor}) when is_binary(actor) do + actor + end + + def get_actor(%{"actor" => actor}) when is_list(actor) do + if is_binary(Enum.at(actor, 0)) do + Enum.at(actor, 0) + else + Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end) + |> Map.get("id") + end + end + + def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do + id + end + + def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do + get_actor(%{"actor" => actor}) + end + + @doc """ + Checks that an imported AP object's actor matches the domain it came from. + """ + def contain_origin(_id, %{"actor" => nil}), do: :error + + def contain_origin(id, %{"actor" => _actor} = params) do + id_uri = URI.parse(id) + actor_uri = URI.parse(get_actor(params)) + + if id_uri.host == actor_uri.host do + :ok + else + :error + end + end + + def contain_origin_from_id(_id, %{"id" => nil}), do: :error + + def contain_origin_from_id(id, %{"id" => other_id} = _params) do + id_uri = URI.parse(id) + other_uri = URI.parse(other_id) + + if id_uri.host == other_uri.host do + :ok + else + :error + end + end +end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex new file mode 100644 index 000000000..138e7866f --- /dev/null +++ b/lib/pleroma/object/fetcher.ex @@ -0,0 +1,75 @@ +defmodule Pleroma.Object.Fetcher do + alias Pleroma.Object + alias Pleroma.Object.Containment + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.OStatus + + require Logger + + @httpoison Application.get_env(:pleroma, :httpoison) + + # TODO: + # This will create a Create activity, which we need internally at the moment. + def fetch_object_from_id(id) do + if object = Object.get_cached_by_ap_id(id) do + {:ok, object} + else + Logger.info("Fetching #{id} via AP") + + with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), + nil <- Object.normalize(data, false), + params <- %{ + "type" => "Create", + "to" => data["to"], + "cc" => data["cc"], + "actor" => data["actor"] || data["attributedTo"], + "object" => data + }, + :ok <- Containment.contain_origin(id, params), + {:ok, activity} <- Transmogrifier.handle_incoming(params) do + {:ok, Object.normalize(activity, false)} + else + {:error, {:reject, nil}} -> + {:reject, nil} + + object = %Object{} -> + {:ok, object} + + _e -> + Logger.info("Couldn't get object via AP, trying out OStatus fetching...") + + case OStatus.fetch_activity_from_url(id) do + {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"], false)} + e -> e + end + end + end + end + + def fetch_object_from_id!(id) do + with {:ok, object} <- fetch_object_from_id(id) do + object + else + _e -> + nil + end + end + + def fetch_and_contain_remote_object_from_id(id) do + Logger.info("Fetching object #{id} via AP") + + with true <- String.starts_with?(id, "http"), + {:ok, %{body: body, status: code}} when code in 200..299 <- + @httpoison.get( + id, + [{:Accept, "application/activity+json"}] + ), + {:ok, data} <- Jason.decode(body), + :ok <- Containment.contain_origin_from_id(id, data) do + {:ok, data} + else + e -> + {:error, e} + end + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index cb88ba308..e77b2b72d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Instances alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Object.Fetcher alias Pleroma.Pagination alias Pleroma.Repo alias Pleroma.Upload @@ -14,7 +15,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator - alias Pleroma.Web.OStatus alias Pleroma.Web.WebFinger import Ecto.Query @@ -95,7 +95,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "type" => "Create" }) do if is_public?(object) do - Activity.increase_replies_count(reply_ap_id) Object.increase_replies_count(reply_ap_id) end end @@ -106,7 +105,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do data: %{"inReplyTo" => reply_ap_id} = object }) do if is_public?(object) do - Activity.decrease_replies_count(reply_ap_id) Object.decrease_replies_count(reply_ap_id) end end @@ -121,7 +119,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, map} <- MRF.filter(map), {recipients, _, _} = get_recipients(map), {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, - {:ok, object} <- insert_full_object(map) do + {:ok, map, object} <- insert_full_object(map) do {:ok, activity} = Repo.insert(%Activity{ data: map, @@ -170,6 +168,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do public = "https://www.w3.org/ns/activitystreams#Public" if activity.data["type"] in ["Create", "Announce", "Delete"] do + object = Object.normalize(activity.data["object"]) Pleroma.Web.Streamer.stream("user", activity) Pleroma.Web.Streamer.stream("list", activity) @@ -181,12 +180,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end if activity.data["type"] in ["Create"] do - activity.data["object"] + object.data |> Map.get("tag", []) |> Enum.filter(fn tag -> is_bitstring(tag) end) |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) - if activity.data["object"]["attachment"] != [] do + if object.data["attachment"] != [] do Pleroma.Web.Streamer.stream("public:media", activity) if activity.local do @@ -572,37 +571,49 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_since(query, _), do: query + defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do + raise "Can't use the child object without preloading!" + end + defp restrict_tag_reject(query, %{"tag_reject" => tag_reject}) when is_list(tag_reject) and tag_reject != [] do from( - activity in query, - where: fragment(~s(\(not \(? #> '{"object","tag"}'\) \\?| ?\)), activity.data, ^tag_reject) + [_activity, object] in query, + where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject) ) end defp restrict_tag_reject(query, _), do: query + defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do + raise "Can't use the child object without preloading!" + end + defp restrict_tag_all(query, %{"tag_all" => tag_all}) when is_list(tag_all) and tag_all != [] do from( - activity in query, - where: fragment(~s(\(? #> '{"object","tag"}'\) \\?& ?), activity.data, ^tag_all) + [_activity, object] in query, + where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all) ) end defp restrict_tag_all(query, _), do: query + defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do + raise "Can't use the child object without preloading!" + end + defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do from( - activity in query, - where: fragment(~s(\(? #> '{"object","tag"}'\) \\?| ?), activity.data, ^tag) + [_activity, object] in query, + where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag) ) end defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do from( - activity in query, - where: fragment(~s(? <@ (? #> '{"object","tag"}'\)), ^tag, activity.data) + [_activity, object] in query, + where: fragment("(?)->'tag' \\? (?)", object.data, ^tag) ) end @@ -667,10 +678,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_favorited_by(query, _), do: query + defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do + raise "Can't use the child object without preloading!" + end + defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do from( - activity in query, - where: fragment(~s(not (? #> '{"object","attachment"}' = ?\)), activity.data, ^[]) + [_activity, object] in query, + where: fragment("not (?)->'attachment' = (?)", object.data, ^[]) ) end @@ -866,7 +881,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def fetch_and_prepare_user_from_ap_id(ap_id) do - with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do + with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do user_data_from_user_object(data) else e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") @@ -976,60 +991,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - # TODO: - # This will create a Create activity, which we need internally at the moment. - def fetch_object_from_id(id) do - if object = Object.get_cached_by_ap_id(id) do - {:ok, object} - else - with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), - nil <- Object.normalize(data), - params <- %{ - "type" => "Create", - "to" => data["to"], - "cc" => data["cc"], - "actor" => data["actor"] || data["attributedTo"], - "object" => data - }, - :ok <- Transmogrifier.contain_origin(id, params), - {:ok, activity} <- Transmogrifier.handle_incoming(params) do - {:ok, Object.normalize(activity)} - else - {:error, {:reject, nil}} -> - {:reject, nil} - - object = %Object{} -> - {:ok, object} - - _e -> - Logger.info("Couldn't get object via AP, trying out OStatus fetching...") - - case OStatus.fetch_activity_from_url(id) do - {:ok, [activity | _]} -> {:ok, Object.normalize(activity)} - e -> e - end - end - end - end - - def fetch_and_contain_remote_object_from_id(id) do - Logger.info("Fetching object #{id} via AP") - - with true <- String.starts_with?(id, "http"), - {:ok, %{body: body, status: code}} when code in 200..299 <- - @httpoison.get( - id, - [{:Accept, "application/activity+json"}] - ), - {:ok, data} <- Jason.decode(body), - :ok <- Transmogrifier.contain_origin_from_id(id, data) do - {:ok, data} - else - e -> - {:error, e} - end - end - # filter out broken threads def contain_broken_threads(%Activity{} = activity, %User{} = user) do entire_thread_visible_for_user?(activity, user) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 3331ebebd..0b80566bf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Activity alias Pleroma.Object + alias Pleroma.Object.Fetcher alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ObjectView @@ -173,7 +174,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do "Signature missing or not from author, relayed Create message, fetching object from source" ) - ActivityPub.fetch_object_from_id(params["object"]["id"]) + Fetcher.fetch_object_from_id(params["object"]["id"]) json(conn, "ok") end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 39cd31921..a80aa52c6 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -8,8 +8,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do """ alias Pleroma.Activity alias Pleroma.Object + alias Pleroma.Object.Containment alias Pleroma.Repo alias Pleroma.User + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -18,56 +20,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do require Logger - def get_actor(%{"actor" => actor}) when is_binary(actor) do - actor - end - - def get_actor(%{"actor" => actor}) when is_list(actor) do - if is_binary(Enum.at(actor, 0)) do - Enum.at(actor, 0) - else - Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end) - |> Map.get("id") - end - end - - def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do - id - end - - def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do - get_actor(%{"actor" => actor}) - end - - @doc """ - Checks that an imported AP object's actor matches the domain it came from. - """ - def contain_origin(_id, %{"actor" => nil}), do: :error - - def contain_origin(id, %{"actor" => _actor} = params) do - id_uri = URI.parse(id) - actor_uri = URI.parse(get_actor(params)) - - if id_uri.host == actor_uri.host do - :ok - else - :error - end - end - - def contain_origin_from_id(_id, %{"id" => nil}), do: :error - - def contain_origin_from_id(id, %{"id" => other_id} = _params) do - id_uri = URI.parse(id) - other_uri = URI.parse(other_id) - - if id_uri.host == other_uri.host do - :ok - else - :error - end - end - @doc """ Modifies an incoming AP object (mastodon format) to our internal format. """ @@ -188,7 +140,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_actor(%{"attributedTo" => actor} = object) do object - |> Map.put("actor", get_actor(%{"actor" => actor})) + |> Map.put("actor", Containment.get_actor(%{"actor" => actor})) end # Check for standardisation @@ -223,7 +175,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "" end - case fetch_obj_helper(in_reply_to_id) do + case get_obj_helper(in_reply_to_id) do {:ok, replied_object} -> with %Activity{} = _activity <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do @@ -448,7 +400,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # - emoji def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) when objtype in ["Article", "Note", "Video", "Page"] do - actor = get_actor(data) + actor = Containment.get_actor(data) data = Map.put(data, "actor", actor) @@ -506,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data ) do - with actor <- get_actor(data), + with actor <- Containment.get_actor(data), %User{} = followed <- User.get_or_fetch_by_ap_id(actor), {:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"), @@ -532,7 +484,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data ) do - with actor <- get_actor(data), + with actor <- Containment.get_actor(data), %User{} = followed <- User.get_or_fetch_by_ap_id(actor), {:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), @@ -556,9 +508,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data ) do - with actor <- get_actor(data), + with actor <- Containment.get_actor(data), %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), + {:ok, object} <- get_obj_helper(object_id), {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do {:ok, activity} else @@ -569,9 +521,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming( %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data ) do - with actor <- get_actor(data), + with actor <- Containment.get_actor(data), %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), + {:ok, object} <- get_obj_helper(object_id), public <- Visibility.is_public?(data), {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do {:ok, activity} @@ -624,10 +576,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do ) do object_id = Utils.get_ap_id(object_id) - with actor <- get_actor(data), + with actor <- Containment.get_actor(data), %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), - :ok <- contain_origin(actor.ap_id, object.data), + {:ok, object} <- get_obj_helper(object_id), + :ok <- Containment.contain_origin(actor.ap_id, object.data), {:ok, activity} <- ActivityPub.delete(object, false) do {:ok, activity} else @@ -643,9 +595,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "id" => id } = data ) do - with actor <- get_actor(data), + with actor <- Containment.get_actor(data), %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), + {:ok, object} <- get_obj_helper(object_id), {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do {:ok, activity} else @@ -713,9 +665,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "id" => id } = data ) do - with actor <- get_actor(data), + with actor <- Containment.get_actor(data), %User{} = actor <- User.get_or_fetch_by_ap_id(actor), - {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), + {:ok, object} <- get_obj_helper(object_id), {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do {:ok, activity} else @@ -725,9 +677,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def handle_incoming(_), do: :error - def fetch_obj_helper(id) when is_bitstring(id), do: ActivityPub.fetch_object_from_id(id) - def fetch_obj_helper(obj) when is_map(obj), do: ActivityPub.fetch_object_from_id(obj["id"]) - def get_obj_helper(id) do if object = Object.normalize(id), do: {:ok, object}, else: nil end @@ -764,9 +713,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # internal -> Mastodon # """ - def prepare_outgoing(%{"type" => "Create", "object" => object} = data) do + def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do object = - object + Object.normalize(object_id).data |> prepare_object data = @@ -827,7 +776,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def maybe_fix_object_url(data) do if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do - case fetch_obj_helper(data["object"]) do + case get_obj_helper(data["object"]) do {:ok, relative_object} -> if relative_object.data["external_url"] do _data = diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index ccc9da7c6..581b9d1ab 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -234,14 +234,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Inserts a full object if it is contained in an activity. """ - def insert_full_object(%{"object" => %{"type" => type} = object_data}) + def insert_full_object(%{"object" => %{"type" => type} = object_data} = map) when is_map(object_data) and type in @supported_object_types do with {:ok, object} <- Object.create(object_data) do - {:ok, object} + map = + map + |> Map.put("object", object.data["id"]) + + {:ok, map, object} end end - def insert_full_object(_), do: {:ok, nil} + def insert_full_object(map), do: {:ok, map, nil} def update_object_in_activities(%{data: %{"id" => id}} = object) do # TODO diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index db52fe933..6dee61dd6 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -41,16 +41,21 @@ defmodule Pleroma.Web.ActivityPub.Visibility do # guard def entire_thread_visible_for_user?(nil, _user), do: false - # child + # XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop + # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength + def entire_thread_visible_for_user?( - %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail, + %Activity{} = tail, + # %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail, user - ) - when is_binary(parent_id) do - parent = Activity.get_in_reply_to_activity(tail) - visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user) + ) do + case Object.normalize(tail) do + %{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) -> + parent = Activity.get_in_reply_to_activity(tail) + visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user) + + _ -> + visible_for_user?(tail, user) + end end - - # root - def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user) end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 74babdf14..6458a3449 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -125,7 +125,10 @@ defmodule Pleroma.Web.CommonAPI do "public" in_reply_to -> - Pleroma.Web.MastodonAPI.StatusView.get_visibility(in_reply_to.data["object"]) + # XXX: these heuristics should be moved out of MastodonAPI. + with %Object{} = object <- Object.normalize(in_reply_to) do + Pleroma.Web.MastodonAPI.StatusView.get_visibility(object) + end end end @@ -214,8 +217,10 @@ defmodule Pleroma.Web.CommonAPI do with %Activity{ actor: ^user_ap_id, data: %{ - "type" => "Create", - "object" => %{ + "type" => "Create" + }, + object: %Object{ + data: %{ "to" => object_to, "type" => "Note" } diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 185292878..25f498fcb 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -208,7 +208,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do context, content_html, attachments, - inReplyTo, + in_reply_to, tags, cw \\ nil, cc \\ [] @@ -225,9 +225,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq() } - if inReplyTo do + if in_reply_to do + in_reply_to_object = Object.normalize(in_reply_to.data["object"]) + object - |> Map.put("inReplyTo", inReplyTo.data["object"]["id"]) + |> Map.put("inReplyTo", in_reply_to_object.data["id"]) else object end diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index c47328e13..1b4deb6dc 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Activity + alias Pleroma.Object.Containment alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Relay @@ -136,7 +137,7 @@ defmodule Pleroma.Web.Federator do # actor shouldn't be acting on objects outside their own AP server. with {:ok, _user} <- ap_enabled_actor(params["actor"]), nil <- Activity.normalize(params["id"]), - :ok <- Transmogrifier.contain_origin_from_id(params["actor"], params), + :ok <- Containment.contain_origin_from_id(params["actor"], params), {:ok, activity} <- Transmogrifier.handle_incoming(params) do {:ok, activity} else diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 63fadce38..3916d7c41 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -4,13 +4,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller - alias Ecto.Changeset alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Filter alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Object.Fetcher alias Pleroma.Pagination alias Pleroma.Repo alias Pleroma.ScheduledActivity @@ -543,10 +543,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id), + with %Activity{} = activity <- Activity.get_by_id_with_object(id), + %Object{} = object <- Object.normalize(activity), %User{} = user <- User.get_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), - {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do + {:ok, user} <- User.bookmark(user, object.data["id"]) do conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -554,10 +555,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id), + with %Activity{} = activity <- Activity.get_by_id_with_object(id), + %Object{} = object <- Object.normalize(activity), %User{} = user <- User.get_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), - {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do + {:ok, user} <- User.unbookmark(user, object.data["id"]) do conn |> put_view(StatusView) |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -681,7 +683,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def favourited_by(conn, %{"id" => id}) do - with %Activity{data: %{"object" => %{"likes" => likes}}} <- Activity.get_by_id(id) do + with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id), + %Object{data: %{"likes" => likes}} <- Object.normalize(object) do q = from(u in User, where: u.ap_id in ^likes) users = Repo.all(q) @@ -694,7 +697,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def reblogged_by(conn, %{"id" => id}) do - with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Activity.get_by_id(id) do + with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id), + %Object{data: %{"announcements" => announces}} <- Object.normalize(object) do q = from(u in User, where: u.ap_id in ^announces) users = Repo.all(q) @@ -997,7 +1001,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def status_search(user, query) do fetched = if Regex.match?(~r/https?:/, query) do - with {:ok, object} <- ActivityPub.fetch_object_from_id(query), + with {:ok, object} <- Fetcher.fetch_object_from_id(query), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user) do [activity] @@ -1008,13 +1012,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do q = from( - a in Activity, + [a, o] in Activity.with_preloaded_object(Activity), where: fragment("?->>'type' = 'Create'", a.data), where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, where: fragment( - "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", - a.data, + "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", + o.data, ^query ), limit: 20, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index a9f607aa5..f8961eb6c 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.Activity alias Pleroma.HTML + alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -19,8 +20,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do defp get_replied_to_activities(activities) do activities |> Enum.map(fn - %{data: %{"type" => "Create", "object" => %{"inReplyTo" => in_reply_to}}} -> - in_reply_to != "" && in_reply_to + %{data: %{"type" => "Create", "object" => object}} -> + object = Object.normalize(object) + object.data["inReplyTo"] != "" && object.data["inReplyTo"] _ -> nil @@ -29,7 +31,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do |> Activity.create_by_object_ap_id() |> Repo.all() |> Enum.reduce(%{}, fn activity, acc -> - Map.put(acc, activity.data["object"]["id"], activity) + object = Object.normalize(activity.data["object"]) + Map.put(acc, object.data["id"], activity) end) end @@ -55,8 +58,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do defp get_context_id(_), do: nil defp reblogged?(activity, user) do - object = activity.data["object"] || %{} - present?(user && user.ap_id in (object["announcements"] || [])) + object = Object.normalize(activity) || %{} + present?(user && user.ap_id in (object.data["announcements"] || [])) end def render("index.json", opts) do @@ -122,14 +125,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do + def render("status.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + object = Object.normalize(activity) + user = get_user(activity.data["actor"]) - like_count = object["like_count"] || 0 - announcement_count = object["announcement_count"] || 0 + like_count = object.data["like_count"] || 0 + announcement_count = object.data["announcement_count"] || 0 - tags = object["tag"] || [] - sensitive = object["sensitive"] || Enum.member?(tags, "nsfw") + tags = object.data["tag"] || [] + sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw") mentions = activity.recipients @@ -137,15 +142,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do |> Enum.filter(& &1) |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end) - favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) - bookmarked = opts[:for] && object["id"] in opts[:for].bookmarks + favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) + + bookmarked = opts[:for] && object.data["id"] in opts[:for].bookmarks - attachment_data = object["attachment"] || [] + attachment_data = object.data["attachment"] || [] attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) - created_at = Utils.to_masto_date(object["published"]) + created_at = Utils.to_masto_date(object.data["published"]) reply_to = get_reply_to(activity, opts) + reply_to_user = reply_to && get_user(reply_to.data["actor"]) content = @@ -167,7 +174,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do "mastoapi:content" ) - summary = object["summary"] || "" + summary = object.data["summary"] || "" summary_html = summary @@ -190,12 +197,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do if user.local do Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) else - object["external_url"] || object["id"] + object.data["external_url"] || object.data["id"] end %{ id: to_string(activity.id), - uri: object["id"], + uri: object.data["id"], url: url, account: AccountView.render("account.json", %{user: user}), in_reply_to_id: reply_to && to_string(reply_to.id), @@ -205,7 +212,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do content: content_html, created_at: created_at, reblogs_count: announcement_count, - replies_count: object["repliesCount"] || 0, + replies_count: object.data["repliesCount"] || 0, favourites_count: like_count, reblogged: reblogged?(activity, opts[:for]), favourited: present?(favorited), @@ -223,7 +230,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do website: nil }, language: nil, - emojis: build_emojis(activity.data["object"]["emoji"]), + emojis: build_emojis(object.data["emoji"]), pleroma: %{ local: activity.local, conversation_id: get_context_id(activity), @@ -305,15 +312,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do - with nil <- replied_to_activities[activity.data["object"]["inReplyTo"]] do + object = Object.normalize(activity.data["object"]) + + with nil <- replied_to_activities[object.data["inReplyTo"]] do # If user didn't participate in the thread Activity.get_in_reply_to_activity(activity) end end - def get_reply_to(%{data: %{"object" => object}}, _) do - if object["inReplyTo"] && object["inReplyTo"] != "" do - Activity.get_create_by_object_ap_id(object["inReplyTo"]) + def get_reply_to(%{data: %{"object" => _object}} = activity, _) do + object = Object.normalize(activity) + + if object.data["inReplyTo"] && object.data["inReplyTo"] != "" do + Activity.get_create_by_object_ap_id(object.data["inReplyTo"]) else nil end @@ -321,8 +332,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def get_visibility(object) do public = "https://www.w3.org/ns/activitystreams#Public" - to = object["to"] || [] - cc = object["cc"] || [] + to = object.data["to"] || [] + cc = object.data["cc"] || [] cond do public in to -> @@ -343,25 +354,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - def render_content(%{"type" => "Video"} = object) do - with name when not is_nil(name) and name != "" <- object["name"] do - "<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}" + def render_content(%{data: %{"type" => "Video"}} = object) do + with name when not is_nil(name) and name != "" <- object.data["name"] do + "<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}" else - _ -> object["content"] || "" + _ -> object.data["content"] || "" end end - def render_content(%{"type" => object_type} = object) + def render_content(%{data: %{"type" => object_type}} = object) when object_type in ["Article", "Page"] do - with summary when not is_nil(summary) and summary != "" <- object["name"], - url when is_bitstring(url) <- object["url"] do - "<p><a href=\"#{url}\">#{summary}</a></p>#{object["content"]}" + with summary when not is_nil(summary) and summary != "" <- object.data["name"], + url when is_bitstring(url) <- object.data["url"] do + "<p><a href=\"#{url}\">#{summary}</a></p>#{object.data["content"]}" else - _ -> object["content"] || "" + _ -> object.data["content"] || "" end end - def render_content(object), do: object["content"] || "" + def render_content(object), do: object.data["content"] || "" @doc """ Builds a dictionary tags. diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 1a1b74bb0..b11a2b5ce 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -54,23 +54,16 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do end) end - defp get_links(%{local: true, data: data}) do + defp get_links(%{local: true}, %{"id" => object_id}) do h = fn str -> [to_charlist(str)] end [ - {:link, [type: ['application/atom+xml'], href: h.(data["object"]["id"]), rel: 'self'], []}, - {:link, [type: ['text/html'], href: h.(data["object"]["id"]), rel: 'alternate'], []} + {:link, [type: ['application/atom+xml'], href: h.(object_id), rel: 'self'], []}, + {:link, [type: ['text/html'], href: h.(object_id), rel: 'alternate'], []} ] end - defp get_links(%{ - local: false, - data: %{ - "object" => %{ - "external_url" => external_url - } - } - }) do + defp get_links(%{local: false}, %{"external_url" => external_url}) do h = fn str -> [to_charlist(str)] end [ @@ -78,7 +71,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do ] end - defp get_links(_activity), do: [] + defp get_links(_activity, _object_data), do: [] defp get_emoji_links(emojis) do Enum.map(emojis, fn {emoji, file} -> @@ -88,14 +81,16 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do def to_simple_form(activity, user, with_author \\ false) - def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user, with_author) do + def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do h = fn str -> [to_charlist(str)] end - updated_at = activity.data["object"]["published"] - inserted_at = activity.data["object"]["published"] + object = Object.normalize(activity.data["object"]) + + updated_at = object.data["published"] + inserted_at = object.data["published"] attachments = - Enum.map(activity.data["object"]["attachment"] || [], fn attachment -> + Enum.map(object.data["attachment"] || [], fn attachment -> url = hd(attachment["url"]) {:link, @@ -108,7 +103,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do mentions = activity.recipients |> get_mentions categories = - (activity.data["object"]["tag"] || []) + (object.data["tag"] || []) |> Enum.map(fn tag -> if is_binary(tag) do {:category, [term: to_charlist(tag)], []} @@ -118,11 +113,11 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do end) |> Enum.filter(& &1) - emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{}) + emoji_links = get_emoji_links(object.data["emoji"] || %{}) summary = - if activity.data["object"]["summary"] do - [{:summary, [], h.(activity.data["object"]["summary"])}] + if object.data["summary"] do + [{:summary, [], h.(object.data["summary"])}] else [] end @@ -131,10 +126,9 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']}, {:"activity:verb", ['http://activitystrea.ms/schema/1.0/post']}, # For notes, federate the object id. - {:id, h.(activity.data["object"]["id"])}, + {:id, h.(object.data["id"])}, {:title, ['New note by #{user.nickname}']}, - {:content, [type: 'html'], - h.(activity.data["object"]["content"] |> String.replace(~r/[\n\r]/, ""))}, + {:content, [type: 'html'], h.(object.data["content"] |> String.replace(~r/[\n\r]/, ""))}, {:published, h.(inserted_at)}, {:updated, h.(updated_at)}, {:"ostatus:conversation", [ref: h.(activity.data["context"])], @@ -142,7 +136,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []} ] ++ summary ++ - get_links(activity) ++ + get_links(activity, object.data) ++ categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links end diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex index db995ec77..ec6e5cfaf 100644 --- a/lib/pleroma/web/ostatus/handlers/note_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex @@ -113,8 +113,9 @@ defmodule Pleroma.Web.OStatus.NoteHandler do cw <- OStatus.get_cw(entry), in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry), in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to), - in_reply_to <- - (in_reply_to_activity && in_reply_to_activity.data["object"]["id"]) || in_reply_to, + in_reply_to_object <- + (in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil, + in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to, attachments <- OStatus.get_attachments(entry), context <- get_context(entry, in_reply_to), tags <- OStatus.get_tags(entry), diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index ed45ca735..9441984c7 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -75,7 +75,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do if is_status?(acct) do - {:ok, object} = ActivityPub.fetch_object_from_id(acct) + {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct) %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"]) redirect(conn, to: "/notice/#{activity_id}") else @@ -101,7 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end defp is_status?(acct) do - case ActivityPub.fetch_and_contain_remote_object_from_id(acct) do + case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do {:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] -> true diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index d6ce0a7c6..8e44dbeb8 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -266,6 +266,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do defp parse_int(_, default), do: default + # TODO: unify the search query with MastoAPI one and do only pagination here def search(_user, %{"q" => query} = params) do limit = parse_int(params["rpp"], 20) page = parse_int(params["page"], 1) @@ -273,13 +274,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do q = from( - a in Activity, + [a, o] in Activity.with_preloaded_object(Activity), where: fragment("?->>'type' = 'Create'", a.data), where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, where: fragment( - "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", - a.data, + "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", + o.data, ^query ), limit: ^limit, diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index ecb2b437b..c64152da8 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -224,15 +224,17 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do def render( "activity.json", - %{activity: %{data: %{"type" => "Create", "object" => object}} = activity} = opts + %{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts ) do user = get_user(activity.data["actor"], opts) - created_at = object["published"] |> Utils.date_to_asctime() - like_count = object["like_count"] || 0 - announcement_count = object["announcement_count"] || 0 - favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) - repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) + object = Object.normalize(object_id) + + created_at = object.data["published"] |> Utils.date_to_asctime() + like_count = object.data["like_count"] || 0 + announcement_count = object.data["announcement_count"] || 0 + favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) + repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || []) pinned = activity.id in user.info.pinned_activities attentions = @@ -245,12 +247,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do conversation_id = get_context_id(activity, opts) - tags = activity.data["object"]["tag"] || [] - possibly_sensitive = activity.data["object"]["sensitive"] || Enum.member?(tags, "nsfw") + tags = object.data["tag"] || [] + possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw") tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags - {summary, content} = render_content(object) + {summary, content} = render_content(object.data) html = content @@ -259,7 +261,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do activity, "twitterapi:content" ) - |> Formatter.emojify(object["emoji"]) + |> Formatter.emojify(object.data["emoji"]) text = if content do @@ -284,7 +286,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do %{ "id" => activity.id, - "uri" => activity.data["object"]["id"], + "uri" => object.data["id"], "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), "statusnet_html" => html, "text" => text, @@ -297,20 +299,20 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do "in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id, "in_reply_to_user_id" => reply_user && reply_user.id, "statusnet_conversation_id" => conversation_id, - "attachments" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts), + "attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts), "attentions" => attentions, "fave_num" => like_count, "repeat_num" => announcement_count, "favorited" => !!favorited, "repeated" => !!repeated, "pinned" => pinned, - "external_url" => object["external_url"] || object["id"], + "external_url" => object.data["external_url"] || object.data["id"], "tags" => tags, "activity_type" => "post", "possibly_sensitive" => possibly_sensitive, "visibility" => StatusView.get_visibility(object), "summary" => summary, - "summary_html" => summary |> Formatter.emojify(object["emoji"]), + "summary_html" => summary |> Formatter.emojify(object.data["emoji"]), "card" => card, "muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user) } |