Resolve merge conflict
authorrinpatch <rinpatch@sdf.org>
Sun, 13 Jan 2019 10:38:28 +0000 (13:38 +0300)
committerrinpatch <rinpatch@sdf.org>
Sun, 13 Jan 2019 10:38:28 +0000 (13:38 +0300)
config/config.exs
lib/pleroma/formatter.ex
lib/pleroma/user.ex
lib/pleroma/web/ostatus/metadata.ex [new file with mode: 0644]
lib/pleroma/web/ostatus/ostatus_controller.ex
lib/pleroma/web/router.ex
mix.exs
test/web/ostatus/ostatus_controller_test.exs

index 1c55807b7cfdbf629262c4dae2bdfb7b33329ec4..895dbb3ab2b8da5db104401df3b26d27a30dd5c5 100644 (file)
@@ -207,6 +207,8 @@ config :pleroma, :gopher,
   ip: {0, 0, 0, 0},
   port: 9999
 
+config :pleroma, :metadata, opengraph: true
+
 config :pleroma, :suggestions,
   enabled: false,
   third_party_engine:
index d80ae6576cd071b3e9b49a35a0bdb43c25d2699c..49f7075e6f1640667bae117e9feb7325ec0d44f1 100644 (file)
@@ -183,4 +183,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
index 68128053903e5f070b3a88413f3010e8a2f9ab7f..3120b13b63bd5018523f42c4fbe0712cdb345cbb 100644 (file)
@@ -404,6 +404,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
@@ -439,11 +443,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) ||
       if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
diff --git a/lib/pleroma/web/ostatus/metadata.ex b/lib/pleroma/web/ostatus/metadata.ex
new file mode 100644 (file)
index 0000000..9935726
--- /dev/null
@@ -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
index 332cbef0e1dfa366d841ed8f30a72d9bc8638586..be648a6ee8bb80ba6fa3fd55eaa5f490982fb4bc 100644 (file)
@@ -20,7 +20,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)
@@ -142,9 +146,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)
index a5f4d812681ce08dba458301b0b6dbb1e8669222..5ef99bec5c5c47cbd49e1db62c6c96ec27d5a42e 100644 (file)
@@ -391,7 +391,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
@@ -409,6 +413,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)
@@ -503,11 +513,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
diff --git a/mix.exs b/mix.exs
index ccf7790b088af1266bc2ae991f6d6e44b471575d..d46998891d15f5688ea178993cc7ce13abdeb325 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -59,6 +59,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"},
index 995cc00d6359973393a0c1f6f5dd9a684d6d0498..8e9d2b69ae7055f6a650efdd6d4a8d51f64b21f3 100644 (file)
@@ -88,6 +88,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
 
     conn =
       conn
+      |> put_req_header("accept", "application/xml")
       |> get(url)
 
     expected =
@@ -114,6 +115,16 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
     |> response(404)
   end
 
+  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
+
   test "404s on deleted objects", %{conn: conn} do
     note_activity = insert(:note_activity)
     [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
@@ -130,15 +141,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
     |> response(404)
   end
 
-  test "gets an activity", %{conn: conn} do
-    note_activity = insert(:note_activity)
-    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
-
-    conn
-    |> get("/activities/#{uuid}")
-    |> response(200)
-  end
-
   test "404s on private activities", %{conn: conn} do
     note_activity = insert(:direct_note_activity)
     [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
@@ -154,7 +156,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