diff options
Diffstat (limited to 'lib/pleroma/web/activity_pub')
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 206 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub_controller.ex | 54 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 298 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/utils.ex | 6 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/views/object_view.ex | 27 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/views/user_view.ex | 57 |
6 files changed, 631 insertions, 17 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 421fd5cd7..965f2cc9b 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1,14 +1,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.{Activity, Repo, Object, Upload, User, Notification} + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.WebFinger + alias Pleroma.Web.Federator + alias Pleroma.Web.OStatus import Ecto.Query import Pleroma.Web.ActivityPub.Utils require Logger + @httpoison Application.get_env(:pleroma, :httpoison) + + def get_recipients(data) do + (data["to"] || []) ++ (data["cc"] || []) + 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 <- insert_full_object(map) do - {:ok, activity} = Repo.insert(%Activity{data: map, local: local, actor: map["actor"]}) + {:ok, activity} = Repo.insert(%Activity{data: map, local: local, actor: map["actor"], recipients: get_recipients(map)}) Notification.create_notifications(activity) stream_out(activity) {:ok, activity} @@ -30,7 +40,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def create(to, actor, context, object, additional \\ %{}, published \\ nil, local \\ true) do + def create(%{to: to, actor: actor, context: context, object: object} = params) do + additional = params[:additional] || %{} + local = !(params[:local] == false) # only accept false as false value + published = params[:published] + with create_data <- make_create_data(%{to: to, actor: actor, published: published, context: context, object: object}, additional), {:ok, activity} <- insert(create_data, local), :ok <- maybe_federate(activity) do @@ -38,6 +52,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + def accept(%{to: to, actor: actor, object: object} = params) do + local = !(params[:local] == false) # only accept false as false value + + with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object}, + {:ok, activity} <- insert(data, local), + :ok <- maybe_federate(activity) do + {:ok, activity} + end + end + + def update(%{to: to, cc: cc, actor: actor, object: object} = params) do + local = !(params[:local] == false) # only accept false as false value + + with data <- %{"to" => to, "cc" => cc, "type" => "Update", "actor" => actor, "object" => object}, + {:ok, activity} <- insert(data, local), + :ok <- maybe_federate(activity) do + {:ok, activity} + end + end + # TODO: This is weird, maybe we shouldn't check here if we can make the activity. def like(%User{ap_id: ap_id} = user, %Object{data: %{"id" => _}} = object, activity_id \\ nil, local \\ true) do with nil <- get_existing_like(ap_id, object), @@ -62,7 +96,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def announce(%User{ap_id: _} = user, %Object{data: %{"id" => _}} = object, activity_id \\ nil, local \\ true) do - with announce_data <- make_announce_data(user, object, activity_id), + with true <- is_public?(object), + announce_data <- make_announce_data(user, object, activity_id), {:ok, activity} <- insert(announce_data, local), {:ok, object} <- add_announce_to_object(activity, object), :ok <- maybe_federate(activity) do @@ -106,16 +141,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def fetch_activities_for_context(context, opts \\ %{}) do - query = from activity in Activity, + public = ["https://www.w3.org/ns/activitystreams#Public"] + recipients = if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public + + query = from activity in Activity + query = query + |> restrict_blocked(opts) + |> restrict_recipients(recipients, opts["user"]) + + query = from activity in query, where: fragment("?->>'type' = ? and ?->>'context' = ?", activity.data, "Create", activity.data, ^context), order_by: [desc: :id] - query = restrict_blocked(query, opts) Repo.all(query) end + # TODO: Make this work properly with unlisted. def fetch_public_activities(opts \\ %{}) do - public = ["https://www.w3.org/ns/activitystreams#Public"] - fetch_activities(public, opts) + q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts) + q + |> Repo.all + |> Enum.reverse end defp restrict_since(query, %{"since_id" => since_id}) do @@ -129,12 +174,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp restrict_tag(query, _), do: query - defp restrict_recipients(query, recipients) do - Enum.reduce(recipients, query, fn (recipient, q) -> - map = %{ to: [recipient] } - from activity in q, - or_where: fragment(~s(? @> ?), activity.data, ^map) - end) + defp restrict_recipients(query, [], user), do: query + defp restrict_recipients(query, recipients, nil) do + from activity in query, + where: fragment("? && ?", ^recipients, activity.recipients) + end + defp restrict_recipients(query, recipients, user) do + from activity in query, + where: fragment("? && ?", ^recipients, activity.recipients), + or_where: activity.actor == ^user.ap_id end defp restrict_local(query, %{"local_only" => true}) do @@ -190,13 +238,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end defp restrict_blocked(query, _), do: query - def fetch_activities(recipients, opts \\ %{}) do + def fetch_activities_query(recipients, opts \\ %{}) do base_query = from activity in Activity, limit: 20, order_by: [fragment("? desc nulls last", activity.id)] base_query - |> restrict_recipients(recipients) + |> restrict_recipients(recipients, opts["user"]) |> restrict_tag(opts) |> restrict_since(opts) |> restrict_local(opts) @@ -207,6 +255,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_recent(opts) |> restrict_blocked(opts) |> restrict_media(opts) + end + + def fetch_activities(recipients, opts \\ %{}) do + fetch_activities_query(recipients, opts) |> Repo.all |> Enum.reverse end @@ -215,4 +267,128 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do data = Upload.store(file) Repo.insert(%Object{data: data}) end + + def user_data_from_user_object(data) do + avatar = data["icon"]["url"] && %{ + "type" => "Image", + "url" => [%{"href" => data["icon"]["url"]}] + } + + banner = data["image"]["url"] && %{ + "type" => "Image", + "url" => [%{"href" => data["image"]["url"]}] + } + + user_data = %{ + ap_id: data["id"], + info: %{ + "ap_enabled" => true, + "source_data" => data, + "banner" => banner + }, + avatar: avatar, + nickname: "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}", + name: data["name"], + follower_address: data["followers"], + bio: data["summary"] + } + + {:ok, user_data} + end + + def fetch_and_prepare_user_from_ap_id(ap_id) do + with {:ok, %{status_code: 200, body: body}} <- @httpoison.get(ap_id, ["Accept": "application/activity+json"]), + {:ok, data} <- Poison.decode(body) do + user_data_from_user_object(data) + else + e -> Logger.error("Could not user at fetch #{ap_id}, #{inspect(e)}") + end + end + + def make_user_from_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 + User.insert_or_update_user(data) + else + e -> {:error, e} + end + end + end + + def make_user_from_nickname(nickname) do + with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do + make_user_from_ap_id(ap_id) + else + _e -> {:error, "No ap id in webfinger"} + end + end + + def publish(actor, activity) do + followers = if actor.follower_address in activity.recipients do + {:ok, followers} = User.get_followers(actor) + followers |> Enum.filter(&(!&1.local)) + else + [] + end + + remote_inboxes = (Pleroma.Web.Salmon.remote_users(activity) ++ followers) + |> Enum.filter(fn (user) -> User.ap_enabled?(user) end) + |> Enum.map(fn (%{info: %{"source_data" => data}}) -> + (data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"] + end) + |> Enum.uniq + + {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + json = Poison.encode!(data) + Enum.each remote_inboxes, fn(inbox) -> + Federator.enqueue(:publish_single_ap, %{inbox: inbox, json: json, actor: actor, id: activity.data["id"]}) + end + end + + def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do + Logger.info("Federating #{id} to #{inbox}") + host = URI.parse(inbox).host + signature = Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)}) + @httpoison.post(inbox, json, [{"Content-Type", "application/activity+json"}, {"signature", signature}]) + 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 + Logger.info("Fetching #{id} via AP") + with {:ok, %{body: body, status_code: code}} when code in 200..299 <- @httpoison.get(id, [Accept: "application/activity+json"], follow_redirect: true, timeout: 10000, recv_timeout: 20000), + {:ok, data} <- Poison.decode(body), + nil <- Object.get_by_ap_id(data["id"]), + params <- %{"type" => "Create", "to" => data["to"], "cc" => data["cc"], "actor" => data["attributedTo"], "object" => data}, + {:ok, activity} <- Transmogrifier.handle_incoming(params) do + {:ok, Object.get_by_ap_id(activity.data["object"]["id"])} + else + 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.get_by_ap_id(activity.data["object"]["id"])} + e -> e + end + end + end + end + + def is_public?(activity) do + "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++ (activity.data["cc"] || [])) + end + + def visible_for_user?(activity, nil) do + is_public?(activity) + end + def visible_for_user?(activity, user) do + x = [user.ap_id | user.following] + y = (activity.data["to"] ++ (activity.data["cc"] || [])) + visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y)) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex new file mode 100644 index 000000000..edbcb938a --- /dev/null +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -0,0 +1,54 @@ +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.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Federator + + require Logger + + action_fallback :errors + + def user(conn, %{"nickname" => nickname}) do + with %User{} = user <- User.get_cached_by_nickname(nickname), + {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(UserView.render("user.json", %{user: user})) + end + end + + def object(conn, %{"uuid" => uuid}) do + with ap_id <- o_status_url(conn, :object, uuid), + %Object{} = object <- Object.get_cached_by_ap_id(ap_id) do + conn + |> put_resp_header("content-type", "application/activity+json") + |> json(ObjectView.render("object.json", %{object: object})) + end + end + + # TODO: Ensure that this inbox is a recipient of the message + def inbox(%{assigns: %{valid_signature: true}} = conn, params) do + Federator.enqueue(:incoming_ap_doc, params) + json(conn, "ok") + end + + def inbox(conn, params) do + headers = Enum.into(conn.req_headers, %{}) + if !(String.contains?(headers["signature"] || "", params["actor"])) do + Logger.info("Signature not from author, relayed message, ignoring.") + else + Logger.info("Signature error.") + Logger.info("Could not validate #{params["actor"]}") + Logger.info(inspect(conn.req_headers)) + end + + json(conn, "ok") + end + + def errors(conn, _e) do + conn + |> put_status(500) + |> json("error") + end +end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex new file mode 100644 index 000000000..37db67798 --- /dev/null +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -0,0 +1,298 @@ +defmodule Pleroma.Web.ActivityPub.Transmogrifier do + @moduledoc """ + A module to handle coding from internal to wire ActivityPub and back. + """ + alias Pleroma.User + alias Pleroma.Object + alias Pleroma.Activity + alias Pleroma.Repo + alias Pleroma.Web.ActivityPub.ActivityPub + + import Ecto.Query + + require Logger + + @doc """ + Modifies an incoming AP object (mastodon format) to our internal format. + """ + def fix_object(object) do + object + |> Map.put("actor", object["attributedTo"]) + |> fix_attachments + |> fix_context + |> fix_in_reply_to + end + + def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object) when not is_nil(in_reply_to_id) do + case ActivityPub.fetch_object_from_id(in_reply_to_id) do + {:ok, replied_object} -> + activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) + object + |> Map.put("inReplyTo", replied_object.data["id"]) + |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) + |> Map.put("inReplyToStatusId", activity.id) + |> Map.put("conversation", replied_object.data["context"] || object["conversation"]) + |> Map.put("context", replied_object.data["context"] || object["conversation"]) + e -> + Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}") + object + end + end + def fix_in_reply_to(object), do: object + + def fix_context(object) do + object + |> Map.put("context", object["conversation"]) + end + + def fix_attachments(object) do + attachments = (object["attachment"] || []) + |> Enum.map(fn (data) -> + url = [%{"type" => "Link", "mediaType" => data["mediaType"], "href" => data["url"]}] + Map.put(data, "url", url) + end) + + object + |> Map.put("attachment", attachments) + end + + # TODO: validate those with a Ecto scheme + # - tags + # - emoji + def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do + with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]), + %User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do + object = fix_object(data["object"]) + + params = %{ + to: data["to"], + object: object, + actor: user, + context: object["conversation"], + local: false, + published: data["published"], + additional: Map.take(data, [ + "cc", + "id" + ]) + } + + + ActivityPub.create(params) + else + %Activity{} = activity -> {:ok, activity} + _e -> :error + end + end + + def handle_incoming(%{"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.follow(follower, followed, id, false) do + ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true}) + User.follow(follower, followed) + {:ok, activity} + else + _e -> :error + end + end + + def handle_incoming(%{"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} + else + _e -> :error + end + end + + def handle_incoming(%{"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} + else + _e -> :error + end + end + + def handle_incoming(%{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} = data) do + with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do + {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object) + + banner = new_user_data[:info]["banner"] + update_data = new_user_data + |> Map.take([:name, :bio, :avatar]) + |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner})) + + actor + |> User.upgrade_changeset(update_data) + |> User.update_and_set_cache() + + ActivityPub.update(%{local: false, to: data["to"] || [], cc: data["cc"] || [], object: object, actor: actor_id}) + else + e -> + Logger.error(e) + :error + end + end + + # TODO: Make secure. + def handle_incoming(%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data) do + object_id = case object_id do + %{"id" => id} -> id + id -> id + end + 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 + + # TODO + # Accept + # Undo + + def handle_incoming(_), do: :error + + def get_obj_helper(id) do + if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil + end + + def prepare_object(object) do + object + |> set_sensitive + |> add_hashtags + |> add_mention_tags + |> add_attributed_to + |> prepare_attachments + |> set_conversation + end + + @doc + """ + internal -> Mastodon + """ + def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do + object = object + |> prepare_object + data = data + |> Map.put("object", object) + |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + + {:ok, data} + end + + def prepare_outgoing(%{"type" => type} = data) do + data = data + |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + + {:ok, data} + end + + def add_hashtags(object) do + tags = (object["tag"] || []) + |> Enum.map fn (tag) -> %{"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", "name" => "##{tag}", "type" => "Hashtag"} end + + object + |> Map.put("tag", tags) + end + + def add_mention_tags(object) do + recipients = object["to"] ++ (object["cc"] || []) + mentions = recipients + |> Enum.map(fn (ap_id) -> User.get_cached_by_ap_id(ap_id) end) + |> Enum.filter(&(&1)) + |> Enum.map(fn(user) -> %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"} end) + + tags = object["tag"] || [] + + object + |> Map.put("tag", tags ++ mentions) + end + + def set_conversation(object) do + Map.put(object, "conversation", object["context"]) + end + + def set_sensitive(object) do + tags = object["tag"] || [] + Map.put(object, "sensitive", "nsfw" in tags) + end + + def add_attributed_to(object) do + attributedTo = object["attributedTo"] || object["actor"] + + object + |> Map.put("attributedTo", attributedTo) + end + + def prepare_attachments(object) do + attachments = (object["attachment"] || []) + |> Enum.map(fn (data) -> + [%{"mediaType" => media_type, "href" => href} | _] = data["url"] + %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"} + end) + + object + |> Map.put("attachment", attachments) + end + + defp user_upgrade_task(user) do + old_follower_address = User.ap_followers(user) + q = from u in User, + where: ^old_follower_address in u.following, + update: [set: [following: fragment("array_replace(?,?,?)", u.following, ^old_follower_address, ^user.follower_address)]] + Repo.update_all(q, []) + + maybe_retire_websub(user.ap_id) + + # Only do this for recent activties, don't go through the whole db. + since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000 + q = from a in Activity, + where: ^old_follower_address in a.recipients, + where: a.id > ^since, + update: [set: [recipients: fragment("array_replace(?,?,?)", a.recipients, ^old_follower_address, ^user.follower_address)]] + Repo.update_all(q, []) + end + + def upgrade_user_from_ap_id(ap_id, async \\ true) do + with %User{local: false} = user <- User.get_by_ap_id(ap_id), + {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do + data = data + |> Map.put(:info, Map.merge(user.info, data[:info])) + + already_ap = User.ap_enabled?(user) + {:ok, user} = User.upgrade_changeset(user, data) + |> Repo.update() + + if !already_ap do + # This could potentially take a long time, do it in the background + if async do + Task.start(fn -> + user_upgrade_task(user) + end) + else + user_upgrade_task(user) + end + end + + {:ok, user} + else + e -> e + end + end + + def maybe_retire_websub(ap_id) do + # some sanity checks + if is_binary(ap_id) && (String.length(ap_id) > 8) do + q = from ws in Pleroma.Web.Websub.WebsubClientSubscription, + where: fragment("? like ?", ws.topic, ^"#{ap_id}%") + Repo.delete_all(q) + end + end +end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index ac20a2822..cda106283 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -68,7 +68,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Inserts a full object if it is contained in an activity. """ - def insert_full_object(%{"object" => object_data}) when is_map(object_data) do + def insert_full_object(%{"object" => %{"type" => type} = object_data}) when is_map(object_data) and type in ["Note"] do with {:ok, _} <- Object.create(object_data) do :ok end @@ -109,6 +109,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "actor" => ap_id, "object" => id, "to" => [actor.follower_address, object.data["actor"]], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], "context" => object.data["context"] } @@ -150,6 +151,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "type" => "Follow", "actor" => follower_id, "to" => [followed_id], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], "object" => followed_id } @@ -177,6 +179,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do "actor" => ap_id, "object" => id, "to" => [user.follower_address, object.data["actor"]], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], "context" => object.data["context"] } @@ -205,7 +208,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do def make_create_data(params, additional) do published = params.published || make_date() - %{ "type" => "Create", "to" => params.to |> Enum.uniq, diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex new file mode 100644 index 000000000..cc0b0556b --- /dev/null +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -0,0 +1,27 @@ +defmodule Pleroma.Web.ActivityPub.ObjectView do + use Pleroma.Web, :view + alias Pleroma.Web.ActivityPub.Transmogrifier + + def render("object.json", %{object: object}) do + base = %{ + "@context" => [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + %{ + "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers", + "sensitive" => "as:sensitive", + "Hashtag" => "as:Hashtag", + "ostatus" => "http://ostatus.org#", + "atomUri" => "ostatus:atomUri", + "inReplyToAtomUri" => "ostatus:inReplyToAtomUri", + "conversation" => "ostatus:conversation", + "toot" => "http://joinmastodon.org/ns#", + "Emoji" => "toot:Emoji" + } + ] + } + + additional = Transmogrifier.prepare_object(object.data) + Map.merge(base, additional) + end +end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex new file mode 100644 index 000000000..179636884 --- /dev/null +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -0,0 +1,57 @@ +defmodule Pleroma.Web.ActivityPub.UserView do + use Pleroma.Web, :view + alias Pleroma.Web.Salmon + alias Pleroma.Web.WebFinger + alias Pleroma.User + + def render("user.json", %{user: user}) do + {:ok, user} = WebFinger.ensure_keys_present(user) + {:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"]) + public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key) + public_key = :public_key.pem_encode([public_key]) + %{ + "@context" => [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + %{ + "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers", + "sensitive" => "as:sensitive", + "Hashtag" => "as:Hashtag", + "ostatus" => "http://ostatus.org#", + "atomUri" => "ostatus:atomUri", + "inReplyToAtomUri" => "ostatus:inReplyToAtomUri", + "conversation" => "ostatus:conversation", + "toot" => "http://joinmastodon.org/ns#", + "Emoji" => "toot:Emoji" + } + ], + "id" => user.ap_id, + "type" => "Person", + "following" => "#{user.ap_id}/following", + "followers" => "#{user.ap_id}/followers", + "inbox" => "#{user.ap_id}/inbox", + "outbox" => "#{user.ap_id}/outbox", + "preferredUsername" => user.nickname, + "name" => user.name, + "summary" => user.bio, + "url" => user.ap_id, + "manuallyApprovesFollowers" => false, + "publicKey" => %{ + "id" => "#{user.ap_id}#main-key", + "owner" => user.ap_id, + "publicKeyPem" => public_key + }, + "endpoints" => %{ + "sharedInbox" => "#{Pleroma.Web.Endpoint.url}/inbox" + }, + "icon" => %{ + "type" => "Image", + "url" => User.avatar_url(user) + }, + "image" => %{ + "type" => "Image", + "url" => User.banner_url(user) + } + } + end +end |