From d9f3af477d687fb8cb469ec652586911ad51d0b0 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 14 Jan 2019 00:05:45 +0100 Subject: [PATCH 1/7] Move definitions of RichMedia fixtures to test/support/http_request_mock.ex --- test/support/http_request_mock.ex | 8 ++++++++ .../controllers/rich_media_controller_test.exs | 17 ++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index e4279e14d..3043d2be6 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -653,6 +653,14 @@ def get("https://social.heldscal.la/user/23211", _, _, Accept: "application/acti {:ok, Tesla.Mock.json(%{"id" => "https://social.heldscal.la/user/23211"}, status: 200)} end + def get("http://example.com/ogp", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}} + end + + def get("http://example.com/empty", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: "hello"}} + end + def get(url, query, body, headers) do {:error, "Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{ diff --git a/test/web/rich_media/controllers/rich_media_controller_test.exs b/test/web/rich_media/controllers/rich_media_controller_test.exs index 37c82631f..fef126513 100644 --- a/test/web/rich_media/controllers/rich_media_controller_test.exs +++ b/test/web/rich_media/controllers/rich_media_controller_test.exs @@ -1,19 +1,14 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.RichMedia.RichMediaControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory + import Tesla.Mock setup do - Tesla.Mock.mock(fn - %{ - method: :get, - url: "http://example.com/ogp" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")} - - %{method: :get, url: "http://example.com/empty"} -> - %Tesla.Env{status: 200, body: "hello"} - end) - + mock(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end From 3f64379b1382f2e26cacd28da230c67bf68656a0 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 14 Jan 2019 00:06:55 +0100 Subject: [PATCH 2/7] Web.MastodonAPI.MastodonAPIController: Add Rich-Media support --- .../mastodon_api/mastodon_api_controller.ex | 23 +++++++++++++++++++ lib/pleroma/web/router.ex | 2 +- .../mastodon_api_controller_test.exs | 16 +++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index f4736fcb5..86607e7af 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1322,6 +1322,29 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do end end + defp status_first_external_url(content) do + content + |> Floki.filter_out("a.mention") + |> Floki.attribute("a", "href") + |> Enum.at(0) + end + + def status_card(conn, %{"id" => status_id}) do + with %Activity{} = activity <- Repo.get(Activity, status_id), + true <- ActivityPub.is_public?(activity), + page_url <- status_first_external_url(activity.data["object"]["content"]), + {:ok, rich_media} <- Pleroma.Web.RichMedia.Parser.parse(page_url) do + card = + rich_media + |> Map.take([:image, :title, :url, :description]) + |> Map.put(:type, "link") + + json(conn, card) + else + _ -> json(conn, %{}) + end + end + def try_render(conn, target, params) when is_binary(target) do res = render(conn, target, params) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b83790858..e749aa834 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -258,7 +258,7 @@ defmodule Pleroma.Web.Router do get("/statuses/:id", MastodonAPIController, :get_status) get("/statuses/:id/context", MastodonAPIController, :get_context) - get("/statuses/:id/card", MastodonAPIController, :empty_object) + get("/statuses/:id/card", MastodonAPIController, :status_card) get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by) get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 6004285d6..bc87383f7 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -1623,5 +1623,21 @@ test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do |> post("/api/v1/statuses/#{activity_two.id}/pin") |> json_response(400) end + + test "Status rich-media Card", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"}) + + response = + conn + |> get("/api/v1/statuses/#{activity.id}/card") + |> json_response(200) + + assert response == %{ + "image" => "http://ia.media-imdb.com/images/rock.jpg", + "title" => "The Rock", + "type" => "link", + "url" => "http://www.imdb.com/title/tt0117500/" + } + end end end From 39863236ebba227d8e742680b739d18d2d211fb0 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 21 Jan 2019 00:53:41 +0100 Subject: [PATCH 3/7] Web.MastodonAPI.MastodonAPIController: generic get_status_card/1 function for MastoAPI 2.6.x Mastodon API 2.6.x added a card key to the Status object so the Card can be shown in the timeline without an extra request at each status. --- .../web/mastodon_api/mastodon_api_controller.ex | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 86607e7af..9d3fa532d 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1329,22 +1329,23 @@ defp status_first_external_url(content) do |> Enum.at(0) end - def status_card(conn, %{"id" => status_id}) do + def get_status_card(status_id) do with %Activity{} = activity <- Repo.get(Activity, status_id), true <- ActivityPub.is_public?(activity), page_url <- status_first_external_url(activity.data["object"]["content"]), {:ok, rich_media} <- Pleroma.Web.RichMedia.Parser.parse(page_url) do - card = - rich_media - |> Map.take([:image, :title, :url, :description]) - |> Map.put(:type, "link") - - json(conn, card) + rich_media + |> Map.take([:image, :title, :url, :description]) + |> Map.put(:type, "link") else - _ -> json(conn, %{}) + _ -> %{} end end + def status_card(conn, %{"id" => status_id}) do + json(conn, get_status_card(status_id)) + end + def try_render(conn, target, params) when is_binary(target) do res = render(conn, target, params) From 78047d57bf52e53c5f073435928983922f9538f5 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 26 Jan 2019 14:47:32 +0000 Subject: [PATCH 4/7] mastodon api: provider_name setting is required too on the card --- lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 9d3fa532d..91cc76fe7 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1337,6 +1337,7 @@ def get_status_card(status_id) do rich_media |> Map.take([:image, :title, :url, :description]) |> Map.put(:type, "link") + |> Map.put(:provider_name, rich_media.site_name) else _ -> %{} end From be9abb2cc5fc219ca49ac6b32afed3eac323bf7a Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 26 Jan 2019 14:55:12 +0000 Subject: [PATCH 5/7] html: add utility function to extract first URL from an object and cache the result --- lib/pleroma/html.ex | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index f5c6e5033..fb602d6b6 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -58,6 +58,20 @@ defp generate_scrubber_signature(scrubbers) do "#{signature}#{to_string(scrubber)}" end) end + + def extract_first_external_url(object, content) do + key = "URL|#{object.id}" + + Cachex.fetch!(:scrubber_cache, key, fn _key -> + result = + content + |> Floki.filter_out("a.mention") + |> Floki.attribute("a", "href") + |> Enum.at(0) + + {:commit, result} + end) + end end defmodule Pleroma.HTML.Scrubber.TwitterText do From 86037e9c3980fbab94935844c09bdd2f002140aa Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 26 Jan 2019 14:56:14 +0000 Subject: [PATCH 6/7] mastodon api: use HTML.extract_first_external_url() --- .../web/mastodon_api/mastodon_api_controller.ex | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 91cc76fe7..ae744da60 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller alias Pleroma.{Repo, Object, Activity, User, Notification, Stats} alias Pleroma.Web + alias Pleroma.HTML alias Pleroma.Web.MastodonAPI.{ StatusView, @@ -1322,22 +1323,18 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do end end - defp status_first_external_url(content) do - content - |> Floki.filter_out("a.mention") - |> Floki.attribute("a", "href") - |> Enum.at(0) - end - def get_status_card(status_id) do with %Activity{} = activity <- Repo.get(Activity, status_id), true <- ActivityPub.is_public?(activity), - page_url <- status_first_external_url(activity.data["object"]["content"]), + %Object{} = object <- Object.normalize(activity.data["object"]), + page_url <- HTML.extract_first_external_url(object, object.data["content"]), {:ok, rich_media} <- Pleroma.Web.RichMedia.Parser.parse(page_url) do + site_name = rich_media[:site_name] || URI.parse(page_url).host + rich_media |> Map.take([:image, :title, :url, :description]) |> Map.put(:type, "link") - |> Map.put(:provider_name, rich_media.site_name) + |> Map.put(:provider_name, site_name) else _ -> %{} end From 1f7843b9b8afcb559c8ba59388724dbf4ef3e3c9 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 26 Jan 2019 15:20:27 +0000 Subject: [PATCH 7/7] mastodon api: use OGP uri instead of page_url for deducing domain name, fix test --- lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 4 +++- test/web/mastodon_api/mastodon_api_controller_test.exs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index ae744da60..a60532b55 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -1329,12 +1329,14 @@ def get_status_card(status_id) do %Object{} = object <- Object.normalize(activity.data["object"]), page_url <- HTML.extract_first_external_url(object, object.data["content"]), {:ok, rich_media} <- Pleroma.Web.RichMedia.Parser.parse(page_url) do + page_url = rich_media[:url] || page_url site_name = rich_media[:site_name] || URI.parse(page_url).host rich_media - |> Map.take([:image, :title, :url, :description]) + |> Map.take([:image, :title, :description]) |> Map.put(:type, "link") |> Map.put(:provider_name, site_name) + |> Map.put(:url, page_url) else _ -> %{} end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index bc87383f7..55e778e4f 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -1634,6 +1634,7 @@ test "Status rich-media Card", %{conn: conn, user: user} do assert response == %{ "image" => "http://ia.media-imdb.com/images/rock.jpg", + "provider_name" => "www.imdb.com", "title" => "The Rock", "type" => "link", "url" => "http://www.imdb.com/title/tt0117500/"