Merge branch 'oembed_provider' into 'develop'
Opengraph/TwitterCard::summary for statuses and user profiles See merge request pleroma/pleroma!533
This commit is contained in:
commit
6383fa3a5d
|
@ -208,6 +208,8 @@
|
||||||
ip: {0, 0, 0, 0},
|
ip: {0, 0, 0, 0},
|
||||||
port: 9999
|
port: 9999
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.Metadata, providers: [], unfurl_nsfw: false
|
||||||
|
|
||||||
config :pleroma, :suggestions,
|
config :pleroma, :suggestions,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
third_party_engine:
|
third_party_engine:
|
||||||
|
|
|
@ -211,3 +211,9 @@ curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerando
|
||||||
* `max_jobs`: The maximum amount of parallel federation jobs running at the same time.
|
* `max_jobs`: The maximum amount of parallel federation jobs running at the same time.
|
||||||
* `initial_timeout`: The initial timeout in seconds
|
* `initial_timeout`: The initial timeout in seconds
|
||||||
* `max_retries`: The maximum number of times a federation job is retried
|
* `max_retries`: The maximum number of times a federation job is retried
|
||||||
|
|
||||||
|
## Pleroma.Web.Metadata
|
||||||
|
* `providers`: a list of metadata providers to enable. Providers availible:
|
||||||
|
* Pleroma.Web.Metadata.Providers.OpenGraph
|
||||||
|
* Pleroma.Web.Metadata.Providers.TwitterCard
|
||||||
|
* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews
|
||||||
|
|
|
@ -43,7 +43,7 @@ def emojify(text) do
|
||||||
|
|
||||||
def emojify(text, nil), do: text
|
def emojify(text, nil), do: text
|
||||||
|
|
||||||
def emojify(text, emoji) do
|
def emojify(text, emoji, strip \\ false) do
|
||||||
Enum.reduce(emoji, text, fn {emoji, file}, text ->
|
Enum.reduce(emoji, text, fn {emoji, file}, text ->
|
||||||
emoji = HTML.strip_tags(emoji)
|
emoji = HTML.strip_tags(emoji)
|
||||||
file = HTML.strip_tags(file)
|
file = HTML.strip_tags(file)
|
||||||
|
@ -51,14 +51,24 @@ def emojify(text, emoji) do
|
||||||
String.replace(
|
String.replace(
|
||||||
text,
|
text,
|
||||||
":#{emoji}:",
|
":#{emoji}:",
|
||||||
|
if not strip do
|
||||||
"<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
|
"<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
|
||||||
MediaProxy.url(file)
|
MediaProxy.url(file)
|
||||||
}' />"
|
}' />"
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
)
|
)
|
||||||
|> HTML.filter_tags()
|
|> HTML.filter_tags()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def demojify(text) do
|
||||||
|
emojify(text, Emoji.get_all(), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def demojify(text, nil), do: text
|
||||||
|
|
||||||
def get_emoji(text) when is_binary(text) do
|
def get_emoji(text) when is_binary(text) do
|
||||||
Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||||
end
|
end
|
||||||
|
@ -189,4 +199,16 @@ def finalize({subs, text}) do
|
||||||
String.replace(result_text, uuid, replacement)
|
String.replace(result_text, uuid, replacement)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def truncate(text, max_length \\ 200, omission \\ "...") do
|
||||||
|
# Remove trailing whitespace
|
||||||
|
text = Regex.replace(~r/([^ \t\r\n])([ \t]+$)/u, text, "\\g{1}")
|
||||||
|
|
||||||
|
if String.length(text) < max_length do
|
||||||
|
text
|
||||||
|
else
|
||||||
|
length_with_omission = max_length - String.length(omission)
|
||||||
|
String.slice(text, 0, length_with_omission) <> omission
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -406,6 +406,10 @@ def locked?(%User{} = user) do
|
||||||
user.info.locked || false
|
user.info.locked || false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_by_id(id) do
|
||||||
|
Repo.get_by(User, id: id)
|
||||||
|
end
|
||||||
|
|
||||||
def get_by_ap_id(ap_id) do
|
def get_by_ap_id(ap_id) do
|
||||||
Repo.get_by(User, ap_id: ap_id)
|
Repo.get_by(User, ap_id: ap_id)
|
||||||
end
|
end
|
||||||
|
@ -441,11 +445,33 @@ def get_cached_by_ap_id(ap_id) do
|
||||||
Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
|
Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_cached_by_id(id) do
|
||||||
|
key = "id:#{id}"
|
||||||
|
|
||||||
|
ap_id =
|
||||||
|
Cachex.fetch!(:user_cache, key, fn _ ->
|
||||||
|
user = get_by_id(id)
|
||||||
|
|
||||||
|
if user do
|
||||||
|
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
|
||||||
|
{:commit, user.ap_id}
|
||||||
|
else
|
||||||
|
{:ignore, ""}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
get_cached_by_ap_id(ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
def get_cached_by_nickname(nickname) do
|
def get_cached_by_nickname(nickname) do
|
||||||
key = "nickname:#{nickname}"
|
key = "nickname:#{nickname}"
|
||||||
Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
|
Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_cached_by_nickname_or_id(nickname_or_id) do
|
||||||
|
get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
|
||||||
|
end
|
||||||
|
|
||||||
def get_by_nickname(nickname) do
|
def get_by_nickname(nickname) do
|
||||||
Repo.get_by(User, nickname: nickname) ||
|
Repo.get_by(User, nickname: nickname) ||
|
||||||
if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
|
if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
|
||||||
|
|
40
lib/pleroma/web/metadata.ex
Normal file
40
lib/pleroma/web/metadata.ex
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Metadata do
|
||||||
|
alias Phoenix.HTML
|
||||||
|
|
||||||
|
def build_tags(params) do
|
||||||
|
Enum.reduce(Pleroma.Config.get([__MODULE__, :providers], []), "", fn parser, acc ->
|
||||||
|
rendered_html =
|
||||||
|
params
|
||||||
|
|> parser.build_tags()
|
||||||
|
|> Enum.map(&to_tag/1)
|
||||||
|
|> Enum.map(&HTML.safe_to_string/1)
|
||||||
|
|> Enum.join()
|
||||||
|
|
||||||
|
acc <> rendered_html
|
||||||
|
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
|
||||||
|
|
||||||
|
def activity_nsfw?(%{data: %{"sensitive" => sensitive}}) do
|
||||||
|
Pleroma.Config.get([__MODULE__, :unfurl_nsfw], false) == false and sensitive
|
||||||
|
end
|
||||||
|
|
||||||
|
def activity_nsfw?(_) do
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
154
lib/pleroma/web/metadata/opengraph.ex
Normal file
154
lib/pleroma/web/metadata/opengraph.ex
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
|
||||||
|
alias Pleroma.Web.Metadata.Providers.Provider
|
||||||
|
alias Pleroma.Web.Metadata
|
||||||
|
alias Pleroma.{HTML, Formatter, User}
|
||||||
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
|
@behaviour Provider
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def build_tags(%{
|
||||||
|
object: object,
|
||||||
|
url: url,
|
||||||
|
user: user
|
||||||
|
}) do
|
||||||
|
attachments = build_attachments(object)
|
||||||
|
scrubbed_content = scrub_html_and_truncate(object)
|
||||||
|
# Zero width space
|
||||||
|
content =
|
||||||
|
if scrubbed_content != "" and scrubbed_content != "\u200B" do
|
||||||
|
": “" <> scrubbed_content <> "”"
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Most previews only show og:title which is inconvenient. Instagram
|
||||||
|
# hacks this by putting the description in the title and making the
|
||||||
|
# description longer prefixed by how many likes and shares the post
|
||||||
|
# has. Here we use the descriptive nickname in the title, and expand
|
||||||
|
# the full account & nickname in the description. We also use the cute^Wevil
|
||||||
|
# smart quotes around the status text like Instagram, too.
|
||||||
|
[
|
||||||
|
{:meta,
|
||||||
|
[
|
||||||
|
property: "og:title",
|
||||||
|
content: "#{user.name}" <> content
|
||||||
|
], []},
|
||||||
|
{:meta, [property: "og:url", content: url], []},
|
||||||
|
{:meta,
|
||||||
|
[
|
||||||
|
property: "og:description",
|
||||||
|
content: "#{user_name_string(user)}" <> content
|
||||||
|
], []},
|
||||||
|
{:meta, [property: "og:type", content: "website"], []}
|
||||||
|
] ++
|
||||||
|
if attachments == [] or Metadata.activity_nsfw?(object) do
|
||||||
|
[
|
||||||
|
{:meta, [property: "og:image", content: attachment_url(User.avatar_url(user))], []},
|
||||||
|
{:meta, [property: "og:image:width", content: 150], []},
|
||||||
|
{:meta, [property: "og:image:height", content: 150], []}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
attachments
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def build_tags(%{user: user}) do
|
||||||
|
with truncated_bio = scrub_html_and_truncate(user.bio || "") do
|
||||||
|
[
|
||||||
|
{:meta,
|
||||||
|
[
|
||||||
|
property: "og:title",
|
||||||
|
content: user_name_string(user)
|
||||||
|
], []},
|
||||||
|
{:meta, [property: "og:url", content: User.profile_url(user)], []},
|
||||||
|
{:meta, [property: "og:description", content: truncated_bio], []},
|
||||||
|
{:meta, [property: "og:type", content: "website"], []},
|
||||||
|
{:meta, [property: "og:image", content: attachment_url(User.avatar_url(user))], []},
|
||||||
|
{:meta, [property: "og:image:width", content: 150], []},
|
||||||
|
{:meta, [property: "og:image:height", content: 150], []}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_attachments(%{data: %{"attachment" => attachments}}) do
|
||||||
|
Enum.reduce(attachments, [], fn attachment, acc ->
|
||||||
|
rendered_tags =
|
||||||
|
Enum.reduce(attachment["url"], [], fn url, acc ->
|
||||||
|
media_type =
|
||||||
|
Enum.find(["image", "audio", "video"], fn media_type ->
|
||||||
|
String.starts_with?(url["mediaType"], media_type)
|
||||||
|
end)
|
||||||
|
|
||||||
|
# TODO: Add additional properties to objects when we have the data available.
|
||||||
|
# Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
|
||||||
|
# object when a Video or GIF is attached it will display that in the Whatsapp Rich Preview.
|
||||||
|
case media_type do
|
||||||
|
"audio" ->
|
||||||
|
[
|
||||||
|
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []}
|
||||||
|
| acc
|
||||||
|
]
|
||||||
|
|
||||||
|
"image" ->
|
||||||
|
[
|
||||||
|
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])],
|
||||||
|
[]},
|
||||||
|
{:meta, [property: "og:image:width", content: 150], []},
|
||||||
|
{:meta, [property: "og:image:height", content: 150], []}
|
||||||
|
| acc
|
||||||
|
]
|
||||||
|
|
||||||
|
"video" ->
|
||||||
|
[
|
||||||
|
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []}
|
||||||
|
| acc
|
||||||
|
]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
acc ++ rendered_tags
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
|
||||||
|
content
|
||||||
|
# html content comes from DB already encoded, decode first and scrub after
|
||||||
|
|> HtmlEntities.decode()
|
||||||
|
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||||
|
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
|
||||||
|
|> Formatter.demojify()
|
||||||
|
|> Formatter.truncate()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp scrub_html_and_truncate(content) when is_binary(content) do
|
||||||
|
content
|
||||||
|
# html content comes from DB already encoded, decode first and scrub after
|
||||||
|
|> HtmlEntities.decode()
|
||||||
|
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||||
|
|> HTML.strip_tags()
|
||||||
|
|> Formatter.demojify()
|
||||||
|
|> Formatter.truncate()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp attachment_url(url) do
|
||||||
|
MediaProxy.url(url)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp user_name_string(user) do
|
||||||
|
"#{user.name} " <>
|
||||||
|
if user.local do
|
||||||
|
"(@#{user.nickname}@#{Pleroma.Web.Endpoint.host()})"
|
||||||
|
else
|
||||||
|
"(@#{user.nickname})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
lib/pleroma/web/metadata/provider.ex
Normal file
7
lib/pleroma/web/metadata/provider.ex
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Metadata.Providers.Provider do
|
||||||
|
@callback build_tags(map()) :: list()
|
||||||
|
end
|
46
lib/pleroma/web/metadata/twitter_card.ex
Normal file
46
lib/pleroma/web/metadata/twitter_card.ex
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
|
||||||
|
alias Pleroma.Web.Metadata.Providers.Provider
|
||||||
|
alias Pleroma.Web.Metadata
|
||||||
|
|
||||||
|
@behaviour Provider
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def build_tags(%{object: object}) do
|
||||||
|
if Metadata.activity_nsfw?(object) or object.data["attachment"] == [] do
|
||||||
|
build_tags(nil)
|
||||||
|
else
|
||||||
|
case find_first_acceptable_media_type(object) do
|
||||||
|
"image" ->
|
||||||
|
[{:meta, [property: "twitter:card", content: "summary_large_image"], []}]
|
||||||
|
|
||||||
|
"audio" ->
|
||||||
|
[{:meta, [property: "twitter:card", content: "player"], []}]
|
||||||
|
|
||||||
|
"video" ->
|
||||||
|
[{:meta, [property: "twitter:card", content: "player"], []}]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
build_tags(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def build_tags(_) do
|
||||||
|
[{:meta, [property: "twitter:card", content: "summary"], []}]
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_first_acceptable_media_type(%{data: %{"attachment" => attachment}}) do
|
||||||
|
Enum.find_value(attachment, fn attachment ->
|
||||||
|
Enum.find_value(attachment["url"], fn url ->
|
||||||
|
Enum.find(["image", "audio", "video"], fn media_type ->
|
||||||
|
String.starts_with?(url["mediaType"], media_type)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||||
|
|
||||||
alias Pleroma.{User, Activity, Object}
|
alias Pleroma.{User, Activity, Object}
|
||||||
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter}
|
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter}
|
||||||
alias Pleroma.Repo
|
|
||||||
alias Pleroma.Web.{OStatus, Federator}
|
alias Pleroma.Web.{OStatus, Federator}
|
||||||
alias Pleroma.Web.XML
|
alias Pleroma.Web.XML
|
||||||
alias Pleroma.Web.ActivityPub.ObjectView
|
alias Pleroma.Web.ActivityPub.ObjectView
|
||||||
|
@ -20,7 +19,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||||
case get_format(conn) do
|
case get_format(conn) do
|
||||||
"html" ->
|
"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" ->
|
"activity+json" ->
|
||||||
ActivityPubController.call(conn, :user)
|
ActivityPubController.call(conn, :user)
|
||||||
|
@ -136,14 +139,27 @@ def activity(conn, %{"uuid" => uuid}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def notice(conn, %{"id" => id}) do
|
def notice(conn, %{"id" => id}) do
|
||||||
with {_, %Activity{} = activity} <- {:activity, Repo.get(Activity, id)},
|
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)},
|
||||||
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
|
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
|
||||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||||
case format = get_format(conn) do
|
case format = get_format(conn) do
|
||||||
"html" ->
|
"html" ->
|
||||||
conn
|
if activity.data["type"] == "Create" do
|
||||||
|> put_resp_content_type("text/html")
|
%Object{} = object = Object.normalize(activity.data["object"])
|
||||||
|> send_file(200, Pleroma.Plugs.InstanceStatic.file_path("index.html"))
|
|
||||||
|
Fallback.RedirectController.redirector_with_meta(conn, %{
|
||||||
|
object: object,
|
||||||
|
url:
|
||||||
|
Pleroma.Web.Router.Helpers.o_status_url(
|
||||||
|
Pleroma.Web.Endpoint,
|
||||||
|
:notice,
|
||||||
|
activity.id
|
||||||
|
),
|
||||||
|
user: user
|
||||||
|
})
|
||||||
|
else
|
||||||
|
Fallback.RedirectController.redirector(conn, nil)
|
||||||
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
represent_activity(conn, format, activity, user)
|
represent_activity(conn, format, activity, user)
|
||||||
|
|
|
@ -396,7 +396,11 @@ defmodule Pleroma.Web.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :ostatus do
|
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
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web do
|
scope "/", Pleroma.Web do
|
||||||
|
@ -414,6 +418,12 @@ defmodule Pleroma.Web.Router do
|
||||||
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
post("/push/subscriptions/:id", Websub.WebsubController, :websub_incoming)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/", Pleroma.Web do
|
||||||
|
pipe_through(:oembed)
|
||||||
|
|
||||||
|
get("/oembed", OEmbed.OEmbedController, :url)
|
||||||
|
end
|
||||||
|
|
||||||
pipeline :activitypub do
|
pipeline :activitypub do
|
||||||
plug(:accepts, ["activity+json"])
|
plug(:accepts, ["activity+json"])
|
||||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||||
|
@ -501,6 +511,7 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
scope "/", Fallback do
|
scope "/", Fallback do
|
||||||
get("/registration/:token", RedirectController, :registration_page)
|
get("/registration/:token", RedirectController, :registration_page)
|
||||||
|
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
|
||||||
get("/*path", RedirectController, :redirector)
|
get("/*path", RedirectController, :redirector)
|
||||||
|
|
||||||
options("/*path", RedirectController, :empty)
|
options("/*path", RedirectController, :empty)
|
||||||
|
@ -509,11 +520,36 @@ defmodule Pleroma.Web.Router do
|
||||||
|
|
||||||
defmodule Fallback.RedirectController do
|
defmodule Fallback.RedirectController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
alias Pleroma.Web.Metadata
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
def redirector(conn, _params) do
|
def redirector(conn, _params) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/html")
|
|> 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, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
|
||||||
|
with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
|
||||||
|
redirector_with_meta(conn, %{user: user})
|
||||||
|
else
|
||||||
|
nil ->
|
||||||
|
redirector(conn, params)
|
||||||
|
end
|
||||||
|
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
|
end
|
||||||
|
|
||||||
def registration_page(conn, params) do
|
def registration_page(conn, params) do
|
||||||
|
|
1
mix.exs
1
mix.exs
|
@ -59,6 +59,7 @@ defp deps do
|
||||||
{:pbkdf2_elixir, "~> 0.12.3"},
|
{:pbkdf2_elixir, "~> 0.12.3"},
|
||||||
{:trailing_format_plug, "~> 0.0.7"},
|
{:trailing_format_plug, "~> 0.0.7"},
|
||||||
{:html_sanitize_ex, "~> 1.3.0"},
|
{:html_sanitize_ex, "~> 1.3.0"},
|
||||||
|
{:html_entities, "~> 0.4"},
|
||||||
{:phoenix_html, "~> 2.10"},
|
{:phoenix_html, "~> 2.10"},
|
||||||
{:calendar, "~> 0.17.4"},
|
{:calendar, "~> 0.17.4"},
|
||||||
{:cachex, "~> 3.0.2"},
|
{:cachex, "~> 3.0.2"},
|
||||||
|
|
94
test/web/metadata/opengraph_test.exs
Normal file
94
test/web/metadata/opengraph_test.exs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Metadata.Providers.OpenGraphTest do
|
||||||
|
use Pleroma.DataCase
|
||||||
|
import Pleroma.Factory
|
||||||
|
alias Pleroma.Web.Metadata.Providers.OpenGraph
|
||||||
|
|
||||||
|
test "it renders all supported types of attachments and skips unknown types" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
note =
|
||||||
|
insert(:note, %{
|
||||||
|
data: %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"tag" => [],
|
||||||
|
"id" => "https://pleroma.gov/objects/whatever",
|
||||||
|
"content" => "pleroma in a nutshell",
|
||||||
|
"attachment" => [
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{"mediaType" => "image/png", "href" => "https://pleroma.gov/tenshi.png"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{
|
||||||
|
"mediaType" => "application/octet-stream",
|
||||||
|
"href" => "https://pleroma.gov/fqa/badapple.sfc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{"mediaType" => "video/webm", "href" => "https://pleroma.gov/about/juche.webm"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{
|
||||||
|
"mediaType" => "audio/basic",
|
||||||
|
"href" => "http://www.gnu.org/music/free-software-song.au"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
|
||||||
|
|
||||||
|
assert Enum.all?(
|
||||||
|
[
|
||||||
|
{:meta, [property: "og:image", content: "https://pleroma.gov/tenshi.png"], []},
|
||||||
|
{:meta,
|
||||||
|
[property: "og:audio", content: "http://www.gnu.org/music/free-software-song.au"],
|
||||||
|
[]},
|
||||||
|
{:meta, [property: "og:video", content: "https://pleroma.gov/about/juche.webm"],
|
||||||
|
[]}
|
||||||
|
],
|
||||||
|
fn element -> element in result end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not render attachments if post is nsfw" do
|
||||||
|
Pleroma.Config.put([Pleroma.Web.Metadata, :unfurl_nsfw], false)
|
||||||
|
user = insert(:user, avatar: %{"url" => [%{"href" => "https://pleroma.gov/tenshi.png"}]})
|
||||||
|
|
||||||
|
note =
|
||||||
|
insert(:note, %{
|
||||||
|
data: %{
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"id" => "https://pleroma.gov/objects/whatever",
|
||||||
|
"content" => "#cuteposting #nsfw #hambaga",
|
||||||
|
"tag" => ["cuteposting", "nsfw", "hambaga"],
|
||||||
|
"sensitive" => true,
|
||||||
|
"attachment" => [
|
||||||
|
%{
|
||||||
|
"url" => [
|
||||||
|
%{"mediaType" => "image/png", "href" => "https://misskey.microsoft/corndog.png"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
|
||||||
|
|
||||||
|
assert {:meta, [property: "og:image", content: "https://pleroma.gov/tenshi.png"], []} in result
|
||||||
|
|
||||||
|
refute {:meta, [property: "og:image", content: "https://misskey.microsoft/corndog.png"], []} in result
|
||||||
|
end
|
||||||
|
end
|
|
@ -88,6 +88,7 @@ test "gets an object", %{conn: conn} do
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|
|> put_req_header("accept", "application/xml")
|
||||||
|> get(url)
|
|> get(url)
|
||||||
|
|
||||||
expected =
|
expected =
|
||||||
|
@ -114,31 +115,34 @@ test "404s on nonexisting objects", %{conn: conn} do
|
||||||
|> response(404)
|
|> response(404)
|
||||||
end
|
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
|
test "404s on deleted objects", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
|
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
|
||||||
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
|
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|> put_req_header("accept", "application/xml")
|
||||||
|> get("/objects/#{uuid}")
|
|> get("/objects/#{uuid}")
|
||||||
|> response(200)
|
|> response(200)
|
||||||
|
|
||||||
Object.delete(object)
|
Object.delete(object)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|> put_req_header("accept", "application/xml")
|
||||||
|> get("/objects/#{uuid}")
|
|> get("/objects/#{uuid}")
|
||||||
|> response(404)
|
|> response(404)
|
||||||
end
|
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
|
test "404s on private activities", %{conn: conn} do
|
||||||
note_activity = insert(:direct_note_activity)
|
note_activity = insert(:direct_note_activity)
|
||||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||||
|
@ -154,7 +158,7 @@ test "404s on nonexistent activities", %{conn: conn} do
|
||||||
|> response(404)
|
|> response(404)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "gets a notice", %{conn: conn} do
|
test "gets a notice in xml format", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
Loading…
Reference in a new issue