diff options
-rw-r--r-- | config/config.exs | 2 | ||||
-rw-r--r-- | lib/pleroma/formatter.ex | 18 | ||||
-rw-r--r-- | lib/pleroma/user.ex | 13 | ||||
-rw-r--r-- | lib/pleroma/web/ostatus/metadata.ex | 82 | ||||
-rw-r--r-- | lib/pleroma/web/ostatus/ostatus_controller.ex | 10 | ||||
-rw-r--r-- | lib/pleroma/web/router.ex | 29 | ||||
-rw-r--r-- | mix.exs | 1 | ||||
-rw-r--r-- | mix.lock | 1 | ||||
-rw-r--r-- | priv/static/index.html | 2 | ||||
-rw-r--r-- | test/web/ostatus/ostatus_controller_test.exs | 19 |
10 files changed, 168 insertions, 9 deletions
diff --git a/config/config.exs b/config/config.exs index e4b31bf81..73242e4ff 100644 --- a/config/config.exs +++ b/config/config.exs @@ -189,6 +189,8 @@ config :pleroma, :gopher, ip: {0, 0, 0, 0}, port: 9999 +config :pleroma, :metadata, opengraph: true + config :pleroma, :suggestions, enabled: false, third_party_engine: diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 46d0d926a..8024d97c2 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -177,4 +177,22 @@ defmodule Pleroma.Formatter do String.replace(result_text, uuid, replacement) end) end + + def truncate(text, opts \\ []) do + max_length = opts[:max_length] || 200 + omission = opts[:omission] || "..." + + cond do + not String.valid?(text) -> + text + + String.length(text) < max_length -> + text + + true -> + length_with_omission = max_length - String.length(omission) + + "#{String.slice(text, 0, length_with_omission)}#{omission}" + end + end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3ad1ab87a..c86ad4afe 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -294,6 +294,10 @@ defmodule Pleroma.User do user.info.locked || false end + def get_by_id(id) do + Repo.get_by(User, id: id) + end + def get_by_ap_id(ap_id) do Repo.get_by(User, ap_id: ap_id) end @@ -320,11 +324,20 @@ defmodule Pleroma.User do Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end) end + def get_cached_by_id(id) do + key = "id:#{id}" + Cachex.fetch!(:user_cache, key, fn _ -> get_by_id(id) end) + end + def get_cached_by_nickname(nickname) do key = "nickname:#{nickname}" Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end) end + def get_cached_by_nickname_or_id(nickname_or_id) do + get_cached_by_nickname(nickname_or_id) || get_cached_by_id(nickname_or_id) + end + def get_by_nickname(nickname) do Repo.get_by(User, nickname: nickname) end diff --git a/lib/pleroma/web/ostatus/metadata.ex b/lib/pleroma/web/ostatus/metadata.ex new file mode 100644 index 000000000..9935726fc --- /dev/null +++ b/lib/pleroma/web/ostatus/metadata.ex @@ -0,0 +1,82 @@ +defmodule Pleroma.Web.Metadata do + alias Phoenix.HTML + alias Pleroma.{Formatter, User} + alias Pleroma.Web.MediaProxy + + def build_tags(params) do + if(meta_enabled?(:opengraph), do: opengraph_tags(params), else: []) + |> Enum.map(&to_tag/1) + |> Enum.map(&HTML.safe_to_string/1) + |> Enum.join("\n") + end + + def meta_enabled?(type) do + Pleroma.Config.get([:metadata, type], false) + end + + # opengraph for single status + defp opengraph_tags(%{activity: activity, user: user}) do + with truncated_content = scrub_html_and_truncate(activity.data["object"]["content"]) do + [ + {:meta, + [ + property: "og:title", + content: "#{user.name} (@#{user.nickname}@#{pleroma_domain()}) post ##{activity.id}" + ], []}, + {:meta, [property: "og:url", content: activity.data["id"]], []}, + {:meta, [property: "og:description", content: truncated_content], []}, + {:meta, [property: "og:image", content: user_avatar_url(user)], []}, + {:meta, [property: "og:image:width", content: 120], []}, + {:meta, [property: "og:image:height", content: 120], []}, + {:meta, [property: "twitter:card", content: "summary"], []} + ] + end + end + + # opengraph for user card + defp opengraph_tags(%{user: user}) do + with truncated_bio = scrub_html_and_truncate(user.bio) do + [ + {:meta, + [ + property: "og:title", + content: "#{user.name} (@#{user.nickname}@#{pleroma_domain()}) profile" + ], []}, + {:meta, [property: "og:url", content: User.profile_url(user)], []}, + {:meta, [property: "og:description", content: truncated_bio], []}, + {:meta, [property: "og:image", content: user_avatar_url(user)], []}, + {:meta, [property: "og:image:width", content: 120], []}, + {:meta, [property: "og:image:height", content: 120], []}, + {:meta, [property: "twitter:card", content: "summary"], []} + ] + end + end + + def to_tag(data) do + with {name, attrs, _content = []} <- data do + HTML.Tag.tag(name, attrs) + else + {name, attrs, content} -> + HTML.Tag.content_tag(name, content, attrs) + + _ -> + raise ArgumentError, message: "make_tag invalid args" + end + end + + defp scrub_html_and_truncate(content) do + content + # html content comes from DB already encoded, decode first and scrub after + |> HtmlEntities.decode() + |> Pleroma.HTML.strip_tags() + |> Formatter.truncate() + end + + defp user_avatar_url(user) do + User.avatar_url(user) |> MediaProxy.url() + end + + def pleroma_domain do + Pleroma.Web.Endpoint.host() + end +end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 6005eadb2..5dbee20e1 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -16,7 +16,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do def feed_redirect(conn, %{"nickname" => nickname}) do case get_format(conn) do "html" -> - Fallback.RedirectController.redirector(conn, nil) + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do + Fallback.RedirectController.redirector_with_meta(conn, %{user: user}) + else + nil -> {:error, :not_found} + end "activity+json" -> ActivityPubController.call(conn, :user) @@ -134,9 +138,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do case format = get_format(conn) do "html" -> - conn - |> put_resp_content_type("text/html") - |> send_file(200, Pleroma.Plugs.InstanceStatic.file_path("index.html")) + Fallback.RedirectController.redirector_with_meta(conn, %{activity: activity, user: user}) _ -> represent_activity(conn, format, activity, user) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index dd1985d6e..49b007422 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -364,7 +364,11 @@ defmodule Pleroma.Web.Router do end pipeline :ostatus do - plug(:accepts, ["xml", "atom", "html", "activity+json"]) + plug(:accepts, ["html", "xml", "atom", "activity+json"]) + end + + pipeline :oembed do + plug(:accepts, ["json", "xml"]) end scope "/", Pleroma.Web do @@ -382,6 +386,12 @@ defmodule Pleroma.Web.Router do post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming) end + scope "/", Pleroma.Web do + pipe_through(:oembed) + + get("/oembed", OEmbed.OEmbedController, :url) + end + pipeline :activitypub do plug(:accepts, ["activity+json"]) plug(Pleroma.Web.Plugs.HTTPSignaturePlug) @@ -455,11 +465,26 @@ end defmodule Fallback.RedirectController do use Pleroma.Web, :controller + alias Pleroma.Web.Metadata def redirector(conn, _params) do conn |> put_resp_content_type("text/html") - |> send_file(200, Pleroma.Plugs.InstanceStatic.file_path("index.html")) + |> send_file(200, index_file_path()) + end + + def redirector_with_meta(conn, params) do + {:ok, index_content} = File.read(index_file_path()) + tags = Metadata.build_tags(params) + response = String.replace(index_content, "<!--server-generated-meta-->", tags) + + conn + |> put_resp_content_type("text/html") + |> send_resp(200, response) + end + + def index_file_path do + Pleroma.Plugs.InstanceStatic.file_path("index.html")) end def registration_page(conn, params) do @@ -54,6 +54,7 @@ defmodule Pleroma.Mixfile do {:pbkdf2_elixir, "~> 0.12.3"}, {:trailing_format_plug, "~> 0.0.7"}, {:html_sanitize_ex, "~> 1.3.0"}, + {:html_entities, "~> 0.4"}, {:phoenix_html, "~> 2.10"}, {:calendar, "~> 0.17.4"}, {:cachex, "~> 3.0.2"}, @@ -23,6 +23,7 @@ "gen_smtp": {:hex, :gen_smtp, "0.13.0", "11f08504c4bdd831dc520b8f84a1dce5ce624474a797394e7aafd3c29f5dcd25", [:rebar3], [], "hexpm"}, "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], [], "hexpm"}, "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/priv/static/index.html b/priv/static/index.html index 02659fdbd..5958e16b1 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.a37e07355ec1b0b45e84807615297c27.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f0b8300215e3fdbb725f.js></script><script type=text/javascript src=/static/js/vendor.b578d16088622c18d886.js></script><script type=text/javascript src=/static/js/app.0220742f52d6912415d5.js></script></body></html>
\ No newline at end of file +<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.ea00efb3229c8591fcc5249f0241b986.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.e076977b8e6c6844fb00.js></script><script type=text/javascript src=/static/js/vendor.cc4190750f6ed4d697bd.js></script><script type=text/javascript src=/static/js/app.67f548ecb9e9fd6b25b0.js></script></body></html> diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index 560305c15..e9e9bdb16 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -84,6 +84,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do conn = conn + |> put_req_header("accept", "application/xml") |> get(url) expected = @@ -110,11 +111,12 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do |> response(404) end - test "gets an activity", %{conn: conn} do + test "gets an activity in xml format", %{conn: conn} do note_activity = insert(:note_activity) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) conn + |> put_req_header("accept", "application/xml") |> get("/activities/#{uuid}") |> response(200) end @@ -134,7 +136,20 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do |> response(404) end - test "gets a notice", %{conn: conn} do + test "renders notice metatags in html format", %{conn: conn} do + note_activity = insert(:note_activity) + conn = get(conn, "/notice/#{note_activity.id}") + body = html_response(conn, 200) + twitter_card_summary = "<meta content=\"summary\" property=\"twitter:card\">" + + description_content = + "<meta content=\"#{note_activity.data["object"]["content"]}\" property=\"og:description\">" + + assert body =~ twitter_card_summary + assert body =~ description_content + end + + test "gets a notice in xml format", %{conn: conn} do note_activity = insert(:note_activity) conn |