diff --git a/installation/pleroma-apache.conf b/installation/pleroma-apache.conf
index d5e75044f..2beb7c4cc 100644
--- a/installation/pleroma-apache.conf
+++ b/installation/pleroma-apache.conf
@@ -1,6 +1,7 @@
# default Apache site config for Pleroma
#
# needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl
+# optional modules: cache cache_disk
#
# Simple installation instructions:
# 1. Install your TLS certificate, possibly using Let's Encrypt.
@@ -8,6 +9,14 @@
# 3. This assumes a Debian style Apache config. Copy this file to
# /etc/apache2/sites-available/ and then add a symlink to it in
# /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache.
+#
+# Optional: enable disk-based caching for the media proxy
+# For details, see https://git.pleroma.social/pleroma/pleroma/wikis/How%20to%20activate%20mediaproxy
+#
+# 1. Create the directory listed below as the CacheRoot, and make sure
+# the Apache user can write to it.
+# 2. Configure Apache's htcacheclean to clean the directory periodically.
+# 3. Run 'a2enmod cache cache_disk' and restart Apache.
Define servername example.tld
@@ -34,6 +43,15 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLCompression off
SSLSessionTickets off
+ # uncomment the following to enable mediaproxy caching on disk
+ #
+ # CacheRoot /var/cache/apache2/mod_cache_disk
+ # CacheDirLevels 1
+ # CacheDirLength 2
+ # CacheEnable disk /proxy
+ # CacheLock on
+ #
+
RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx
index a3d55e4bf..66a0e987c 100644
--- a/installation/pleroma.nginx
+++ b/installation/pleroma.nginx
@@ -11,7 +11,7 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cac
server {
server_name example.tld;
- listen 80;
+ listen [::]:80;
return 301 https://$server_name$request_uri;
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure
@@ -29,7 +29,7 @@ server {
ssl_session_cache shared:ssl_session_cache:10m;
server {
- listen 443 ssl http2;
+ listen [::]:443 ssl http2;
ssl_session_timeout 5m;
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index 32cb817d2..ba9614029 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -37,6 +37,7 @@ def init([ip, port]) do
defmodule Pleroma.Gopher.Server.ProtocolHandler do
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.User
@@ -110,7 +111,7 @@ def response("/main/all") do
def response("/notices/" <> id) do
with %Activity{} = activity <- Repo.get(Activity, id),
- true <- ActivityPub.is_public?(activity) do
+ true <- Visibility.is_public?(activity) do
activities =
ActivityPub.fetch_activities_for_context(activity.data["context"])
|> render_activities
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 7e153f396..cc255cc9e 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
import Ecto.Query
import Pleroma.Web.ActivityPub.Utils
+ import Pleroma.Web.ActivityPub.Visibility
require Logger
@@ -912,57 +913,6 @@ def fetch_and_contain_remote_object_from_id(id) do
end
end
- def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
- def is_public?(%Object{data: data}), do: is_public?(data)
- def is_public?(%Activity{data: data}), do: is_public?(data)
- def is_public?(%{"directMessage" => true}), do: false
-
- def is_public?(data) do
- "https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
- end
-
- def is_private?(activity) do
- unless is_public?(activity) do
- follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
- Enum.any?(activity.data["to"], &(&1 == follower_address))
- else
- false
- end
- end
-
- def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
- def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
-
- def is_direct?(activity) do
- !is_public?(activity) && !is_private?(activity)
- end
-
- def visible_for_user?(activity, nil) do
- is_public?(activity)
- end
-
- def visible_for_user?(activity, user) do
- x = [user.ap_id | user.following]
- y = activity.data["to"] ++ (activity.data["cc"] || [])
- visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
- end
-
- # guard
- def entire_thread_visible_for_user?(nil, _user), do: false
-
- # child
- def entire_thread_visible_for_user?(
- %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
- user
- )
- when is_binary(parent_id) do
- parent = Activity.get_in_reply_to_activity(tail)
- visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
- end
-
- # root
- def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)
-
# filter out broken threads
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
entire_thread_visible_for_user?(activity, user)
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 2bea51311..ff924a536 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
@@ -49,7 +50,7 @@ def user(conn, %{"nickname" => nickname}) do
def object(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, ActivityPub.is_public?(object)} do
+ {_, true} <- {:public?, Visibility.is_public?(object)} do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("object.json", %{object: object}))
@@ -62,7 +63,7 @@ def object(conn, %{"uuid" => uuid}) do
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, ActivityPub.is_public?(object)},
+ {_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do
{page, _} = Integer.parse(page)
@@ -78,7 +79,7 @@ def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
def object_likes(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :object, uuid),
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, ActivityPub.is_public?(object)},
+ {_, true} <- {:public?, Visibility.is_public?(object)},
likes <- Utils.get_object_likes(object) do
conn
|> put_resp_header("content-type", "application/activity+json")
@@ -92,7 +93,7 @@ def object_likes(conn, %{"uuid" => uuid}) do
def activity(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :activity, uuid),
%Activity{} = activity <- Activity.normalize(ap_id),
- {_, true} <- {:public?, ActivityPub.is_public?(activity)} do
+ {_, true} <- {:public?, Visibility.is_public?(activity)} do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("object.json", %{object: activity}))
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 41d89a02b..88007aa16 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Repo
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.ActivityPub.Visibility
import Ecto.Query
@@ -489,7 +490,7 @@ def handle_incoming(
with actor <- get_actor(data),
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
- public <- ActivityPub.is_public?(data),
+ public <- Visibility.is_public?(data),
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
{:ok, activity}
else
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index c8e154989..415cbd47a 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -188,14 +188,24 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
end
activities = ActivityPub.fetch_user_activities(user, nil, params)
- min_id = Enum.at(Enum.reverse(activities), 0).id
- max_id = Enum.at(activities, 0).id
- collection =
- Enum.map(activities, fn act ->
- {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
- data
- end)
+ {max_id, min_id, collection} =
+ if length(activities) > 0 do
+ {
+ Enum.at(Enum.reverse(activities), 0).id,
+ Enum.at(activities, 0).id,
+ Enum.map(activities, fn act ->
+ {:ok, data} = Transmogrifier.prepare_outgoing(act.data)
+ data
+ end)
+ }
+ else
+ {
+ 0,
+ 0,
+ []
+ }
+ end
iri = "#{user.ap_id}/outbox"
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
new file mode 100644
index 000000000..db52fe933
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -0,0 +1,56 @@
+defmodule Pleroma.Web.ActivityPub.Visibility do
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
+
+ def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
+ def is_public?(%Object{data: data}), do: is_public?(data)
+ def is_public?(%Activity{data: data}), do: is_public?(data)
+ def is_public?(%{"directMessage" => true}), do: false
+
+ def is_public?(data) do
+ "https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
+ end
+
+ def is_private?(activity) do
+ unless is_public?(activity) do
+ follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address
+ Enum.any?(activity.data["to"], &(&1 == follower_address))
+ else
+ false
+ end
+ end
+
+ def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
+ def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
+
+ def is_direct?(activity) do
+ !is_public?(activity) && !is_private?(activity)
+ end
+
+ def visible_for_user?(activity, nil) do
+ is_public?(activity)
+ end
+
+ def visible_for_user?(activity, user) do
+ x = [user.ap_id | user.following]
+ y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
+ visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
+ end
+
+ # guard
+ def entire_thread_visible_for_user?(nil, _user), do: false
+
+ # child
+ def entire_thread_visible_for_user?(
+ %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
+ user
+ )
+ when is_binary(parent_id) do
+ parent = Activity.get_in_reply_to_activity(tail)
+ visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
+ end
+
+ # root
+ def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)
+end
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex
index d4e2a9742..fbfe97dbc 100644
--- a/lib/pleroma/web/federator/federator.ex
+++ b/lib/pleroma/web/federator/federator.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Web.Websub
alias Pleroma.Web.Salmon
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
@@ -94,7 +95,7 @@ def perform(:publish, activity) do
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, actor} = WebFinger.ensure_keys_present(actor)
- if ActivityPub.is_public?(activity) do
+ if Visibility.is_public?(activity) do
if OStatus.is_representable?(activity) do
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 60738301b..12987442a 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -27,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.MastodonAPI.ReportView
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
@@ -307,7 +308,7 @@ def dm_timeline(%{assigns: %{user: user}} = conn, params) do
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id),
- true <- ActivityPub.visible_for_user?(activity, user) do
+ true <- Visibility.visible_for_user?(activity, user) do
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user})
@@ -449,7 +450,7 @@ def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id),
%User{} = user <- User.get_by_nickname(user.nickname),
- true <- ActivityPub.visible_for_user?(activity, user),
+ true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do
conn
|> put_view(StatusView)
@@ -460,7 +461,7 @@ def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Repo.get(Activity, id),
%User{} = user <- User.get_by_nickname(user.nickname),
- true <- ActivityPub.visible_for_user?(activity, user),
+ true <- Visibility.visible_for_user?(activity, user),
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do
conn
|> put_view(StatusView)
@@ -867,7 +868,7 @@ def status_search(user, query) do
if Regex.match?(~r/https?:/, query) do
with {:ok, object} <- ActivityPub.fetch_object_from_id(query),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
- true <- ActivityPub.visible_for_user?(activity, user) do
+ true <- Visibility.visible_for_user?(activity, user) do
[activity]
else
_e -> []
@@ -1518,9 +1519,9 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
end
end
- def status_card(conn, %{"id" => status_id}) do
+ def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
with %Activity{} = activity <- Repo.get(Activity, status_id),
- true <- ActivityPub.is_public?(activity) do
+ true <- Visibility.visible_for_user?(activity, user) do
data =
StatusView.render(
"card.json",
diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex
index 190377767..cafb8134b 100644
--- a/lib/pleroma/web/metadata/opengraph.ex
+++ b/lib/pleroma/web/metadata/opengraph.ex
@@ -3,12 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
- alias Pleroma.HTML
- alias Pleroma.Formatter
alias Pleroma.User
alias Pleroma.Web.Metadata
- alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Metadata.Providers.Provider
+ alias Pleroma.Web.Metadata.Utils
@behaviour Provider
@@ -19,7 +17,7 @@ def build_tags(%{
user: user
}) do
attachments = build_attachments(object)
- scrubbed_content = scrub_html_and_truncate(object)
+ scrubbed_content = Utils.scrub_html_and_truncate(object)
# Zero width space
content =
if scrubbed_content != "" and scrubbed_content != "\u200B" do
@@ -44,13 +42,14 @@ def build_tags(%{
{:meta,
[
property: "og:description",
- content: "#{user_name_string(user)}" <> content
+ content: "#{Utils.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", content: Utils.attachment_url(User.avatar_url(user))],
+ []},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
]
@@ -61,17 +60,17 @@ def build_tags(%{
@impl Provider
def build_tags(%{user: user}) do
- with truncated_bio = scrub_html_and_truncate(user.bio || "") do
+ with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
[
{:meta,
[
property: "og:title",
- content: user_name_string(user)
+ content: Utils.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", content: Utils.attachment_url(User.avatar_url(user))], []},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
]
@@ -93,14 +92,15 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
case media_type do
"audio" ->
[
- {:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []}
+ {:meta,
+ [property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
| acc
]
"image" ->
[
- {:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])],
- []},
+ {:meta,
+ [property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []},
{:meta, [property: "og:image:width", content: 150], []},
{:meta, [property: "og:image:height", content: 150], []}
| acc
@@ -108,7 +108,8 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
"video" ->
[
- {:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []}
+ {:meta,
+ [property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}
| acc
]
@@ -120,37 +121,4 @@ defp build_attachments(%{data: %{"attachment" => attachments}}) do
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/
/, " ")
- |> 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/
/, " ")
- |> 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
diff --git a/lib/pleroma/web/metadata/player_view.ex b/lib/pleroma/web/metadata/player_view.ex
new file mode 100644
index 000000000..e9a8cfc8d
--- /dev/null
+++ b/lib/pleroma/web/metadata/player_view.ex
@@ -0,0 +1,21 @@
+defmodule Pleroma.Web.Metadata.PlayerView do
+ use Pleroma.Web, :view
+ import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2]
+
+ def render("player.html", %{"mediaType" => type, "href" => href}) do
+ {tag_type, tag_attrs} =
+ case type do
+ "audio" <> _ -> {:audio, []}
+ "video" <> _ -> {:video, [loop: true]}
+ end
+
+ content_tag(
+ tag_type,
+ [
+ tag(:source, src: href, type: type),
+ "Your browser does not support #{type} playback."
+ ],
+ [controls: true] ++ tag_attrs
+ )
+ end
+end
diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex
index 32b979357..a0be383e5 100644
--- a/lib/pleroma/web/metadata/twitter_card.ex
+++ b/lib/pleroma/web/metadata/twitter_card.ex
@@ -3,44 +3,120 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
- alias Pleroma.Web.Metadata.Providers.Provider
+ alias Pleroma.User
alias Pleroma.Web.Metadata
+ alias Pleroma.Web.Metadata.Providers.Provider
+ alias Pleroma.Web.Metadata.Utils
@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)
+ def build_tags(%{
+ activity_id: id,
+ object: object,
+ user: user
+ }) do
+ attachments = build_attachments(id, object)
+ scrubbed_content = Utils.scrub_html_and_truncate(object)
+ # Zero width space
+ content =
+ if scrubbed_content != "" and scrubbed_content != "\u200B" do
+ "“" <> scrubbed_content <> "”"
+ else
+ ""
+ end
+
+ [
+ {:meta,
+ [
+ property: "twitter:title",
+ content: Utils.user_name_string(user)
+ ], []},
+ {:meta,
+ [
+ property: "twitter:description",
+ content: content
+ ], []}
+ ] ++
+ if attachments == [] or Metadata.activity_nsfw?(object) do
+ [
+ {:meta,
+ [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []},
+ {:meta, [property: "twitter:card", content: "summary_large_image"], []}
+ ]
+ else
+ attachments
end
- end
end
@impl Provider
- def build_tags(_) do
- [{:meta, [property: "twitter:card", content: "summary"], []}]
+ def build_tags(%{user: user}) do
+ with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
+ [
+ {:meta,
+ [
+ property: "twitter:title",
+ content: Utils.user_name_string(user)
+ ], []},
+ {:meta, [property: "twitter:description", content: truncated_bio], []},
+ {:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))],
+ []},
+ {:meta, [property: "twitter:card", content: "summary"], []}
+ ]
+ end
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)
+ defp build_attachments(id, %{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.
+ case media_type do
+ "audio" ->
+ [
+ {:meta, [property: "twitter:card", content: "player"], []},
+ {:meta, [property: "twitter:player:width", content: "480"], []},
+ {:meta, [property: "twitter:player:height", content: "80"], []},
+ {:meta, [property: "twitter:player", content: player_url(id)], []}
+ | acc
+ ]
+
+ "image" ->
+ [
+ {:meta, [property: "twitter:card", content: "summary_large_image"], []},
+ {:meta,
+ [
+ property: "twitter:player",
+ content: Utils.attachment_url(url["href"])
+ ], []}
+ | acc
+ ]
+
+ # TODO: Need the true width and height values here or Twitter renders an iFrame with a bad aspect ratio
+ "video" ->
+ [
+ {:meta, [property: "twitter:card", content: "player"], []},
+ {:meta, [property: "twitter:player", content: player_url(id)], []},
+ {:meta, [property: "twitter:player:width", content: "480"], []},
+ {:meta, [property: "twitter:player:height", content: "480"], []}
+ | acc
+ ]
+
+ _ ->
+ acc
+ end
end)
- end)
+
+ acc ++ rendered_tags
end)
end
+
+ defp player_url(id) do
+ Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id)
+ end
end
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
new file mode 100644
index 000000000..a166800d4
--- /dev/null
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright \xc2\xa9 2017-2019 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Metadata.Utils do
+ alias Pleroma.HTML
+ alias Pleroma.Formatter
+ alias Pleroma.Web.MediaProxy
+
+ def 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/
/, " ")
+ |> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
+ |> Formatter.demojify()
+ |> Formatter.truncate()
+ end
+
+ def 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/
/, " ")
+ |> HTML.strip_tags()
+ |> Formatter.demojify()
+ |> Formatter.truncate()
+ end
+
+ def attachment_url(url) do
+ MediaProxy.url(url)
+ end
+
+ def user_name_string(user) do
+ "#{user.name} " <>
+ if user.local do
+ "(@#{user.nickname}@#{Pleroma.Web.Endpoint.host()})"
+ else
+ "(@#{user.nickname})"
+ end
+ end
+end
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index ee2e3d6ec..4e963774a 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.OStatus.ActivityRepresenter
@@ -102,7 +103,7 @@ def object(conn, %{"uuid" => uuid}) do
else
with id <- o_status_url(conn, :object, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)},
- {_, true} <- {:public?, ActivityPub.is_public?(activity)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
@@ -127,7 +128,7 @@ def activity(conn, %{"uuid" => uuid}) do
else
with id <- o_status_url(conn, :activity, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
- {_, true} <- {:public?, ActivityPub.is_public?(activity)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
@@ -148,7 +149,7 @@ def activity(conn, %{"uuid" => uuid}) do
def notice(conn, %{"id" => id}) do
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)},
- {_, true} <- {:public?, ActivityPub.is_public?(activity)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do
"html" ->
@@ -156,6 +157,7 @@ def notice(conn, %{"id" => id}) do
%Object{} = object = Object.normalize(activity.data["object"])
Fallback.RedirectController.redirector_with_meta(conn, %{
+ activity_id: activity.id,
object: object,
url:
Pleroma.Web.Router.Helpers.o_status_url(
@@ -187,6 +189,30 @@ def notice(conn, %{"id" => id}) do
end
end
+ # Returns an HTML embedded