diff --git a/lib/federation/activity_stream/converter/event.ex b/lib/federation/activity_stream/converter/event.ex index fcb5e31fa..b415b4e8a 100644 --- a/lib/federation/activity_stream/converter/event.ex +++ b/lib/federation/activity_stream/converter/event.ex @@ -10,7 +10,6 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do alias Mobilizon.Addresses alias Mobilizon.Addresses.Address alias Mobilizon.Events.Event, as: EventModel - alias Mobilizon.Medias.Media alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.Converter.Address, as: AddressConverter @@ -21,7 +20,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do fetch_tags: 1, fetch_mentions: 1, build_tags: 1, - maybe_fetch_actor_and_attributed_to_id: 1 + maybe_fetch_actor_and_attributed_to_id: 1, + process_pictures: 2 ] require Logger @@ -34,6 +34,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do defdelegate model_to_as(event), to: EventConverter end + @online_address_name "Website" + @banner_picture_name "Banner" + @doc """ Converts an AP object data to our internal data structure. """ @@ -47,30 +50,16 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do {:tags, tags} <- {:tags, fetch_tags(object["tag"])}, {:mentions, mentions} <- {:mentions, fetch_mentions(object["tag"])}, {:visibility, visibility} <- {:visibility, get_visibility(object)}, - {:options, options} <- {:options, get_options(object)} do - attachments = - object - |> Map.get("attachment", []) - |> Enum.filter(fn attachment -> Map.get(attachment, "type", "Document") == "Document" end) - - picture_id = - with true <- length(attachments) > 0, - {:ok, %Media{id: picture_id}} <- - attachments - |> hd() - |> MediaConverter.find_or_create_media(actor_id) do - picture_id - else - _err -> - nil - end - + {:options, options} <- {:options, get_options(object)}, + [description: description, picture_id: picture_id, medias: medias] <- + process_pictures(object, actor_id) do %{ title: object["name"], - description: object["content"], + description: description, organizer_actor_id: actor_id, attributed_to_id: if(is_nil(attributed_to), do: nil, else: attributed_to.id), picture_id: picture_id, + medias: medias, begins_on: object["startTime"], ends_on: object["endTime"], category: object["category"], @@ -143,6 +132,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do |> maybe_add_physical_address(event) |> maybe_add_event_picture(event) |> maybe_add_online_address(event) + |> maybe_add_inline_media(event) end # Get only elements that we have in EventOptions @@ -213,7 +203,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do "type" => "Link", "href" => url, "mediaType" => "text/html", - "name" => "Website" + "name" => @online_address_name } -> url @@ -239,7 +229,12 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do res, "attachment", [], - &(&1 ++ [MediaConverter.model_to_as(event.picture)]) + &(&1 ++ + [ + event.picture + |> MediaConverter.model_to_as() + |> Map.put("name", @banner_picture_name) + ]) ) end @@ -258,9 +253,21 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do "type" => "Link", "href" => event.online_address, "mediaType" => "text/html", - "name" => "Website" + "name" => @online_address_name } ]) ) end + + @spec maybe_add_inline_media(map(), Event.t()) :: map() + defp maybe_add_inline_media(res, event) do + medias = Enum.map(event.media, &MediaConverter.model_to_as/1) + + Map.update( + res, + "attachment", + [], + &(&1 ++ medias) + ) + end end diff --git a/lib/federation/activity_stream/converter/post.ex b/lib/federation/activity_stream/converter/post.ex index 823c471e0..7798a3a8c 100644 --- a/lib/federation/activity_stream/converter/post.ex +++ b/lib/federation/activity_stream/converter/post.ex @@ -9,9 +9,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Utils alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} + alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter alias Mobilizon.Posts.Post require Logger + import Mobilizon.Federation.ActivityStream.Converter.Utils, + only: [ + process_pictures: 2 + ] + @behaviour Converter defimpl Convertible, for: Post do @@ -20,6 +26,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do defdelegate model_to_as(post), to: PostConverter end + @banner_picture_name "Banner" + @doc """ Convert an post struct to an ActivityStream representation """ @@ -35,8 +43,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do "name" => post.title, "content" => post.body, "attributedTo" => creator_url, - "published" => (post.publish_at || post.inserted_at) |> to_date() + "published" => (post.publish_at || post.inserted_at) |> to_date(), + "attachment" => [] } + |> maybe_add_post_picture(post) + |> maybe_add_inline_media(post) end @doc """ @@ -48,15 +59,19 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do %{"type" => "Article", "actor" => creator, "attributedTo" => group} = object ) do with {:ok, %Actor{id: attributed_to_id}} <- get_actor(group), - {:ok, %Actor{id: author_id}} <- get_actor(creator) do + {:ok, %Actor{id: author_id}} <- get_actor(creator), + [description: description, picture_id: picture_id, medias: medias] <- + process_pictures(object, attributed_to_id) do %{ title: object["name"], - body: object["content"], + body: description, url: object["id"], attributed_to_id: attributed_to_id, author_id: author_id, local: false, - publish_at: object["published"] + publish_at: object["published"], + picture_id: picture_id, + medias: medias } else {:error, err} -> {:error, err} @@ -70,4 +85,34 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do defp to_date(%DateTime{} = date), do: DateTime.to_iso8601(date) defp to_date(%NaiveDateTime{} = date), do: NaiveDateTime.to_iso8601(date) + + @spec maybe_add_post_picture(map(), Post.t()) :: map() + defp maybe_add_post_picture(res, post) do + if is_nil(post.picture), + do: res, + else: + Map.update( + res, + "attachment", + [], + &(&1 ++ + [ + post.picture + |> MediaConverter.model_to_as() + |> Map.put("name", @banner_picture_name) + ]) + ) + end + + @spec maybe_add_inline_media(map(), Post.t()) :: map() + defp maybe_add_inline_media(res, post) do + medias = Enum.map(post.media, &MediaConverter.model_to_as/1) + + Map.update( + res, + "attachment", + [], + &(&1 ++ medias) + ) + end end diff --git a/lib/federation/activity_stream/converter/utils.ex b/lib/federation/activity_stream/converter/utils.ex index b06c9fa3f..beb08fe03 100644 --- a/lib/federation/activity_stream/converter/utils.ex +++ b/lib/federation/activity_stream/converter/utils.ex @@ -6,15 +6,19 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do alias Mobilizon.{Actors, Events} alias Mobilizon.Actors.Actor alias Mobilizon.Events.Tag + alias Mobilizon.Medias.Media alias Mobilizon.Mention alias Mobilizon.Storage.Repo alias Mobilizon.Federation.ActivityPub + alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter alias Mobilizon.Web.Endpoint require Logger + @banner_picture_name "Banner" + @spec fetch_tags([String.t()]) :: [Tag.t()] def fetch_tags(tags) when is_list(tags) do Logger.debug("fetching tags") @@ -169,4 +173,62 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do actor end end + + @spec process_pictures(map(), integer()) :: Keyword.t() + def process_pictures(object, actor_id) do + attachements = Map.get(object, "attachment", []) + + media_attachements = get_medias(attachements) + + media_attachements_map = + media_attachements + |> Enum.map(fn media_attachement -> + {media_attachement["url"], + MediaConverter.find_or_create_media(media_attachement, actor_id)} + end) + |> Enum.reduce(%{}, fn {old_url, media}, acc -> + case media do + {:ok, %Media{} = media} -> + Map.put(acc, old_url, media) + + _ -> + acc + end + end) + + media_attachements_map_urls = + media_attachements_map + |> Enum.map(fn {old_url, new_media} -> {old_url, new_media.file.url} end) + |> Map.new() + + picture_id = + with banner when is_map(banner) <- get_banner_picture(attachements), + {:ok, %Media{id: picture_id}} <- + MediaConverter.find_or_create_media(banner, actor_id) do + picture_id + else + _err -> + nil + end + + description = replace_media_urls_in_body(object["content"], media_attachements_map_urls) + [description: description, picture_id: picture_id, medias: Map.values(media_attachements_map)] + end + + defp replace_media_urls_in_body(body, media_urls), + do: + Enum.reduce(media_urls, body, fn media_url, body -> + replace_media_url_in_body(body, media_url) + end) + + defp replace_media_url_in_body(body, {old_url, new_url}), + do: String.replace(body, old_url, new_url) + + defp get_medias(attachments) do + Enum.filter(attachments, &(&1["type"] == "Document" && &1["name"] != @banner_picture_name)) + end + + defp get_banner_picture(attachments) do + Enum.find(attachments, &(&1["type"] == "Document" && &1["name"] == @banner_picture_name)) + end end diff --git a/lib/mobilizon/posts/posts.ex b/lib/mobilizon/posts/posts.ex index d8da3e4a4..b75e835c5 100644 --- a/lib/mobilizon/posts/posts.ex +++ b/lib/mobilizon/posts/posts.ex @@ -10,7 +10,7 @@ defmodule Mobilizon.Posts do import Ecto.Query require Logger - @post_preloads [:author, :attributed_to, :picture] + @post_preloads [:author, :attributed_to, :picture, :media] import EctoEnum diff --git a/lib/web/views/page_view.ex b/lib/web/views/page_view.ex index b70a4ff43..92e625d3f 100644 --- a/lib/web/views/page_view.ex +++ b/lib/web/views/page_view.ex @@ -8,6 +8,7 @@ defmodule Mobilizon.Web.PageView do alias Mobilizon.Actors.Actor alias Mobilizon.Discussions.{Comment, Discussion} alias Mobilizon.Events.Event + alias Mobilizon.Posts.Post alias Mobilizon.Resources.Resource alias Mobilizon.Tombstone @@ -54,6 +55,12 @@ defmodule Mobilizon.Web.PageView do |> Map.merge(Utils.make_json_ld_header()) end + def render("post.activity-json", %{conn: %{assigns: %{object: %Post{} = post}}}) do + post + |> Convertible.model_to_as() + |> Map.merge(Utils.make_json_ld_header()) + end + def render(page, %{object: object, conn: conn} = _assigns) when page in ["actor.html", "event.html", "comment.html", "post.html"] do locale = get_locale(conn)