X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Fmastodon_api%2Fviews%2Fstatus_view.ex;h=4d4681da828c5f41e0be47fd41f541db1c0c9f83;hb=0fd176b990887c170928bc0ce7d43d3a0aab8f7f;hp=3eca43a922ce954ade38301508fd18d8da2548ab;hpb=046741c60d46d5f4de5356af79022bcb05bbaad6;p=akkoma diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3eca43a92..62d064d71 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -1,34 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.MastodonAPI.StatusView do use Pleroma.Web, :view - alias Pleroma.Web.MastodonAPI.{AccountView, StatusView} - alias Pleroma.{User, Activity} + + alias Pleroma.Activity + alias Pleroma.HTML + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy - alias Pleroma.Repo # TODO: Add cached version. defp get_replied_to_activities(activities) do activities |> Enum.map(fn - %{data: %{"type" => "Create", "object" => %{"inReplyTo" => inReplyTo}}} -> - inReplyTo != "" && inReplyTo + %{data: %{"type" => "Create", "object" => object}} -> + object = Object.normalize(object) + object.data["inReplyTo"] != "" && object.data["inReplyTo"] _ -> nil end) |> Enum.filter(& &1) - |> Activity.create_activity_by_object_id_query() + |> 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) + Map.put(acc, object.data["id"], activity) end) end + defp get_user(ap_id) do + cond do + user = User.get_cached_by_ap_id(ap_id) -> + user + + user = User.get_by_guessed_nickname(ap_id) -> + user + + true -> + User.error_user(ap_id) + end + end + + defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id), + do: context_id + + defp get_context_id(%{data: %{"context" => context}}) when is_binary(context), + do: Utils.context_to_conversation_id(context) + + defp get_context_id(_), do: nil + + defp reblogged?(activity, user) do + object = Object.normalize(activity) || %{} + present?(user && user.ap_id in (object.data["announcements"] || [])) + end + def render("index.json", opts) do replied_to_activities = get_replied_to_activities(opts.activities) - render_many( - opts.activities, + opts.activities + |> safe_render_many( StatusView, "status.json", Map.put(opts, :replied_to_activities, replied_to_activities) @@ -39,11 +77,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do "status.json", %{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts ) do - user = User.get_cached_by_ap_id(activity.data["actor"]) + user = get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) - reblogged = Activity.get_create_activity_by_object_ap_id(object) - reblogged = render("status.json", Map.put(opts, :activity, reblogged)) + reblogged_activity = Activity.get_create_by_object_ap_id(object) + reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity)) + + activity_object = Object.normalize(activity) + favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || []) + + bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], reblogged_activity) mentions = activity.recipients @@ -59,36 +102,44 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do in_reply_to_id: nil, in_reply_to_account_id: nil, reblog: reblogged, - content: reblogged[:content], + content: reblogged[:content] || "", created_at: created_at, reblogs_count: 0, + replies_count: 0, favourites_count: 0, - reblogged: false, - favourited: false, + reblogged: reblogged?(reblogged_activity, opts[:for]), + favourited: present?(favorited), + bookmarked: present?(bookmarked), muted: false, + pinned: pinned?(activity, user), sensitive: false, spoiler_text: "", visibility: "public", - media_attachments: [], + media_attachments: reblogged[:media_attachments] || [], mentions: mentions, - tags: [], + tags: reblogged[:tags] || [], application: %{ name: "Web", website: nil }, language: nil, - emojis: [] + emojis: [], + pleroma: %{ + local: activity.local + } } end - def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do - user = User.get_cached_by_ap_id(activity.data["actor"]) + 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 @@ -96,65 +147,153 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do |> Enum.filter(& &1) |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end) - repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) - favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) + favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - attachment_data = object["attachment"] || [] - attachment_data = attachment_data ++ if object["type"] == "Video", do: [object], else: [] + bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], activity) + + 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 && User.get_cached_by_ap_id(reply_to.data["actor"]) - - emojis = - (activity.data["object"]["emoji"] || []) - |> Enum.map(fn {name, url} -> - name = HtmlSanitizeEx.strip_tags(name) - url = - HtmlSanitizeEx.strip_tags(url) - |> MediaProxy.url() + reply_to_user = reply_to && get_user(reply_to.data["actor"]) - %{shortcode: name, url: url, static_url: url} - end) + content = + object + |> render_content() + + content_html = + content + |> HTML.get_cached_scrubbed_html_for_activity( + User.html_filter_policy(opts[:for]), + activity, + "mastoapi:content" + ) + + content_plaintext = + content + |> HTML.get_cached_stripped_html_for_activity( + activity, + "mastoapi:content" + ) + + summary = object.data["summary"] || "" + + summary_html = + summary + |> HTML.get_cached_scrubbed_html_for_activity( + User.html_filter_policy(opts[:for]), + activity, + "mastoapi:summary" + ) + + summary_plaintext = + summary + |> HTML.get_cached_stripped_html_for_activity( + activity, + "mastoapi:summary" + ) + + card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)) + + url = + if user.local do + Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) + else + object.data["external_url"] || object.data["id"] + end %{ id: to_string(activity.id), - uri: object["id"], - url: object["external_url"] || 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), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), reblog: nil, - content: render_content(object), + card: card, + content: content_html, created_at: created_at, reblogs_count: announcement_count, + replies_count: object.data["repliesCount"] || 0, favourites_count: like_count, - reblogged: !!repeated, - favourited: !!favorited, - muted: false, + reblogged: reblogged?(activity, opts[:for]), + favourited: present?(favorited), + bookmarked: present?(bookmarked), + muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user), + pinned: pinned?(activity, user), sensitive: sensitive, - spoiler_text: object["summary"] || "", + spoiler_text: summary_html, visibility: get_visibility(object), - media_attachments: attachments |> Enum.take(4), + media_attachments: attachments, mentions: mentions, - # fix, - tags: [], + tags: build_tags(tags), application: %{ name: "Web", website: nil }, language: nil, - emojis: emojis + emojis: build_emojis(object.data["emoji"]), + pleroma: %{ + local: activity.local, + conversation_id: get_context_id(activity), + in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, + content: %{"text/plain" => content_plaintext}, + spoiler_text: %{"text/plain" => summary_plaintext} + } + } + end + + def render("status.json", _) do + nil + end + + def render("card.json", %{rich_media: rich_media, page_url: page_url}) do + page_url_data = URI.parse(page_url) + + page_url_data = + if rich_media[:url] != nil do + URI.merge(page_url_data, URI.parse(rich_media[:url])) + else + page_url_data + end + + page_url = page_url_data |> to_string + + image_url = + if rich_media[:image] != nil do + URI.merge(page_url_data, URI.parse(rich_media[:image])) + |> to_string + else + nil + end + + site_name = rich_media[:site_name] || page_url_data.host + + %{ + type: "link", + provider_name: site_name, + provider_url: page_url_data.scheme <> "://" <> page_url_data.host, + url: page_url, + image: image_url |> MediaProxy.url(), + title: rich_media[:title], + description: rich_media[:description], + pleroma: %{ + opengraph: rich_media + } } end + def render("card.json", _) do + nil + end + def render("attachment.json", %{attachment: attachment}) do [attachment_url | _] = attachment["url"] - media_type = attachment_url["mediaType"] || attachment_url["mimeType"] - href = attachment_url["href"] + media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image" + href = attachment_url["href"] |> MediaProxy.url() type = cond do @@ -168,23 +307,30 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do %{ id: to_string(attachment["id"] || hash_id), - url: MediaProxy.url(href), + url: href, remote_url: href, - preview_url: MediaProxy.url(href), + preview_url: href, text_url: href, type: type, - description: attachment["name"] + description: attachment["name"], + pleroma: %{mime_type: media_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"]] + object = Object.normalize(activity) + + 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_activity_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 @@ -192,8 +338,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 -> @@ -206,38 +352,89 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do Enum.any?(to, &String.contains?(&1, "/followers")) -> "private" + length(cc) > 0 -> + "private" + true -> "direct" end end - def render_content(%{"type" => "Video"} = object) do - name = object["name"] - - content = - if !!name and name != "" do - "

#{name}

#{object["content"]}" - else - object["content"] - end + def render_content(%{data: %{"type" => "Video"}} = object) do + with name when not is_nil(name) and name != "" <- object.data["name"] do + "

#{name}

#{object.data["content"]}" + else + _ -> object.data["content"] || "" + end + end - HtmlSanitizeEx.basic_html(content) + 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.data["name"], + url when is_bitstring(url) <- object.data["url"] do + "

#{summary}

#{object.data["content"]}" + else + _ -> object.data["content"] || "" + end end - def render_content(%{"type" => "Article"} = object) do - summary = object["name"] + def render_content(object), do: object.data["content"] || "" - content = - if !!summary and summary != "" do - "

#{summary}

#{object["content"]}" - else - object["content"] - end + @doc """ + Builds a dictionary tags. + + ## Examples + + iex> Pleroma.Web.MastodonAPI.StatusView.build_tags(["fediverse", "nextcloud"]) + [{"name": "fediverse", "url": "/tag/fediverse"}, + {"name": "nextcloud", "url": "/tag/nextcloud"}] - HtmlSanitizeEx.basic_html(content) + """ + @spec build_tags(list(any())) :: list(map()) + def build_tags(object_tags) when is_list(object_tags) do + object_tags = for tag when is_binary(tag) <- object_tags, do: tag + + Enum.reduce(object_tags, [], fn tag, tags -> + tags ++ [%{name: tag, url: "/tag/#{tag}"}] + end) end - def render_content(object) do - HtmlSanitizeEx.basic_html(object["content"]) + def build_tags(_), do: [] + + @doc """ + Builds list emojis. + + Arguments: `nil` or list tuple of name and url. + + Returns list emojis. + + ## Examples + + iex> Pleroma.Web.MastodonAPI.StatusView.build_emojis([{"2hu", "corndog.png"}]) + [%{shortcode: "2hu", static_url: "corndog.png", url: "corndog.png", visible_in_picker: false}] + + """ + @spec build_emojis(nil | list(tuple())) :: list(map()) + def build_emojis(nil), do: [] + + def build_emojis(emojis) do + emojis + |> Enum.map(fn {name, url} -> + name = HTML.strip_tags(name) + + url = + url + |> HTML.strip_tags() + |> MediaProxy.url() + + %{shortcode: name, url: url, static_url: url, visible_in_picker: false} + end) end + + defp present?(nil), do: false + defp present?(false), do: false + defp present?(_), do: true + + defp pinned?(%Activity{id: id}, %User{info: %{pinned_activities: pinned_activities}}), + do: id in pinned_activities end