initial implementation
This commit is contained in:
parent
cb6e7359af
commit
e8226ca0c3
10 changed files with 197 additions and 0 deletions
|
@ -407,6 +407,8 @@
|
||||||
accept: [],
|
accept: [],
|
||||||
reject: []
|
reject: []
|
||||||
|
|
||||||
|
config :pleroma, :mrf_inline_quote, prefix: "Quote"
|
||||||
|
|
||||||
# threshold of 7 days
|
# threshold of 7 days
|
||||||
config :pleroma, :mrf_object_age,
|
config :pleroma, :mrf_object_age,
|
||||||
threshold: 604_800,
|
threshold: 604_800,
|
||||||
|
|
|
@ -292,6 +292,13 @@ def get_in_reply_to_activity(%Activity{} = activity) do
|
||||||
get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
|
get_in_reply_to_activity_from_object(Object.normalize(activity, fetch: false))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_quoted_activity_from_object(%Object{data: %{"quoteUri" => ap_id}}) do
|
||||||
|
IO.puts(ap_id)
|
||||||
|
get_create_by_object_ap_id_with_object(ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_quoted_activity_from_object(_), do: nil
|
||||||
|
|
||||||
def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
|
def normalize(%Activity{data: %{"id" => ap_id}}), do: get_by_ap_id_with_object(ap_id)
|
||||||
def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
|
def normalize(%{"id" => ap_id}), do: get_by_ap_id_with_object(ap_id)
|
||||||
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
|
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
|
||||||
|
|
|
@ -168,6 +168,7 @@ def note(%ActivityDraft{} = draft) do
|
||||||
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
||||||
}
|
}
|
||||||
|> add_in_reply_to(draft.in_reply_to)
|
|> add_in_reply_to(draft.in_reply_to)
|
||||||
|
|> add_quote(draft.quote)
|
||||||
|> Map.merge(draft.extra)
|
|> Map.merge(draft.extra)
|
||||||
|
|
||||||
{:ok, data, []}
|
{:ok, data, []}
|
||||||
|
@ -183,6 +184,16 @@ defp add_in_reply_to(object, in_reply_to) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp add_quote(object, nil), do: object
|
||||||
|
|
||||||
|
defp add_quote(object, quote) do
|
||||||
|
with %Object{} = quote_object <- Object.normalize(quote, fetch: false) do
|
||||||
|
Map.put(object, "quoteUri", quote_object.data["id"])
|
||||||
|
else
|
||||||
|
_ -> object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def answer(user, object, name) do
|
def answer(user, object, name) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
|
|
71
lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
Normal file
71
lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do
|
||||||
|
@moduledoc "Force a quote line into the message content."
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
defp build_inline_quote(prefix, url) do
|
||||||
|
"<span class=\"quote-inline\"><br/><br/>#{prefix}: <a href=\"#{url}\">#{url}</a></span>"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp has_inline_quote?(content, quote_url) do
|
||||||
|
cond do
|
||||||
|
# Does the quote URL exist in the content?
|
||||||
|
content =~ quote_url -> true
|
||||||
|
# Does the content already have a .quote-inline span?
|
||||||
|
content =~ "<span class=\"quote-inline\">" -> true
|
||||||
|
# No inline quote found
|
||||||
|
true -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp filter_object(%{"quoteUrl" => quote_url} = object) do
|
||||||
|
content = object["content"] || ""
|
||||||
|
|
||||||
|
if has_inline_quote?(content, quote_url) do
|
||||||
|
object
|
||||||
|
else
|
||||||
|
prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix])
|
||||||
|
|
||||||
|
content =
|
||||||
|
if String.ends_with?(content, "</p>"),
|
||||||
|
do:
|
||||||
|
String.trim_trailing(content, "</p>") <>
|
||||||
|
build_inline_quote(prefix, quote_url) <> "</p>",
|
||||||
|
else: content <> build_inline_quote(prefix, quote_url)
|
||||||
|
|
||||||
|
Map.put(object, "content", content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do
|
||||||
|
{:ok, Map.put(activity, "object", filter_object(object))}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: {:ok, %{}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_inline_quote,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy",
|
||||||
|
label: "MRF Inline Quote",
|
||||||
|
description: "Force quote post URLs inline",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :prefix,
|
||||||
|
type: :string,
|
||||||
|
description: "Prefix before the link",
|
||||||
|
suggestions: ["RT", "QT", "RE", "RN"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -43,6 +43,7 @@ def fix_object(object, options \\ []) do
|
||||||
|> fix_content_map()
|
|> fix_content_map()
|
||||||
|> fix_addressing()
|
|> fix_addressing()
|
||||||
|> fix_summary()
|
|> fix_summary()
|
||||||
|
|> fix_quote_url()
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_summary(%{"summary" => nil} = object) do
|
def fix_summary(%{"summary" => nil} = object) do
|
||||||
|
@ -879,6 +880,43 @@ defp strip_internal_tags(%{"tag" => tags} = object) do
|
||||||
|
|
||||||
defp strip_internal_tags(object), do: object
|
defp strip_internal_tags(object), do: object
|
||||||
|
|
||||||
|
def fix_quote_url(object, options \\ [])
|
||||||
|
|
||||||
|
def fix_quote_url(%{"quoteUri" => quote_url} = object, options)
|
||||||
|
when not is_nil(quote_url) do
|
||||||
|
with {:ok, quoted_object} <- get_obj_helper(quote_url, options),
|
||||||
|
%Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do
|
||||||
|
Map.put(object, "quoteUri", quoted_object.data["id"])
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.warn("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}")
|
||||||
|
object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Soapbox
|
||||||
|
def fix_quote_url(%{"quoteUrl" => quote_url} = object, options) do
|
||||||
|
object
|
||||||
|
|> Map.put("quoteUri", quote_url)
|
||||||
|
|> fix_quote_url(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Old Fedibird (bug)
|
||||||
|
# https://github.com/fedibird/mastodon/issues/9
|
||||||
|
def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do
|
||||||
|
object
|
||||||
|
|> Map.put("quoteUri", quote_url)
|
||||||
|
|> fix_quote_url(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_quote_url(%{"_misskey_quote" => quote_url} = object, options) do
|
||||||
|
object
|
||||||
|
|> Map.put("quoteUri", quote_url)
|
||||||
|
|> fix_quote_url(options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_quote_url(object, _), do: object
|
||||||
|
|
||||||
def perform(:user_upgrade, user) do
|
def perform(:user_upgrade, user) do
|
||||||
# we pass a fake user so that the followers collection is stripped away
|
# we pass a fake user so that the followers collection is stripped away
|
||||||
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
||||||
|
|
|
@ -496,6 +496,12 @@ defp create_request do
|
||||||
type: :string,
|
type: :string,
|
||||||
description:
|
description:
|
||||||
"Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
|
"Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
|
||||||
|
},
|
||||||
|
quote_id: %Schema{
|
||||||
|
nullable: true,
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"Will quote a given status."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
|
|
|
@ -132,6 +132,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
pinned: %Schema{
|
pinned: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Have you pinned this status? Only appears if the status is pinnable."
|
description: "Have you pinned this status? Only appears if the status is pinnable."
|
||||||
|
},
|
||||||
|
quote_id: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "ID of the status being quoted",
|
||||||
|
nullable: true
|
||||||
|
},
|
||||||
|
quote: %Schema{
|
||||||
|
|
||||||
},
|
},
|
||||||
pleroma: %Schema{
|
pleroma: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
|
@ -204,6 +212,25 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
akkoma: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
source: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
content: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "The source content of the status"
|
||||||
|
},
|
||||||
|
mediaType: %{
|
||||||
|
type: :string,
|
||||||
|
description: "The source MIME type of the status",
|
||||||
|
example: "text/plain"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"},
|
poll: %Schema{allOf: [Poll], nullable: true, description: "The poll attached to the status"},
|
||||||
reblog: %Schema{
|
reblog: %Schema{
|
||||||
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
|
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
|
||||||
|
|
|
@ -22,6 +22,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
attachments: [],
|
attachments: [],
|
||||||
in_reply_to: nil,
|
in_reply_to: nil,
|
||||||
in_reply_to_conversation: nil,
|
in_reply_to_conversation: nil,
|
||||||
|
quote_id: nil,
|
||||||
|
quote: nil,
|
||||||
visibility: nil,
|
visibility: nil,
|
||||||
expires_at: nil,
|
expires_at: nil,
|
||||||
extra: nil,
|
extra: nil,
|
||||||
|
@ -37,6 +39,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
||||||
preview?: false,
|
preview?: false,
|
||||||
changes: %{}
|
changes: %{}
|
||||||
|
|
||||||
|
|
||||||
def new(user, params) do
|
def new(user, params) do
|
||||||
%__MODULE__{user: user}
|
%__MODULE__{user: user}
|
||||||
|> put_params(params)
|
|> put_params(params)
|
||||||
|
@ -54,6 +57,7 @@ def create(user, params) do
|
||||||
|> with_valid(&in_reply_to/1)
|
|> with_valid(&in_reply_to/1)
|
||||||
|> with_valid(&in_reply_to_conversation/1)
|
|> with_valid(&in_reply_to_conversation/1)
|
||||||
|> with_valid(&visibility/1)
|
|> with_valid(&visibility/1)
|
||||||
|
|> with_valid("e_id/1)
|
||||||
|> content()
|
|> content()
|
||||||
|> with_valid(&to_and_cc/1)
|
|> with_valid(&to_and_cc/1)
|
||||||
|> with_valid(&context/1)
|
|> with_valid(&context/1)
|
||||||
|
@ -108,6 +112,18 @@ defp in_reply_to_conversation(draft) do
|
||||||
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
|
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp quote_id(%{params: %{quote_id: ""}} = draft), do: draft
|
||||||
|
|
||||||
|
defp quote_id(%{params: %{quote_id: id}} = draft) when is_binary(id) do
|
||||||
|
%__MODULE__{draft | quote: Activity.get_by_id(id)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp quote_id(%{params: %{quote_id: %Activity{} = quote}} = draft) do
|
||||||
|
%__MODULE__{draft | quote: quote}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp quote_id(draft), do: draft
|
||||||
|
|
||||||
defp visibility(%{params: params} = draft) do
|
defp visibility(%{params: params} = draft) do
|
||||||
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
||||||
{visibility, "direct"} when visibility != "direct" ->
|
{visibility, "direct"} when visibility != "direct" ->
|
||||||
|
|
|
@ -112,6 +112,7 @@ def perform(:incoming_ap_doc, params) do
|
||||||
e ->
|
e ->
|
||||||
# Just drop those for now
|
# Just drop those for now
|
||||||
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
|
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
|
||||||
|
IO.inspect(e)
|
||||||
{:error, e}
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -329,6 +329,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
|
|
||||||
{pinned?, pinned_at} = pin_data(object, user)
|
{pinned?, pinned_at} = pin_data(object, user)
|
||||||
|
|
||||||
|
quote = Activity.get_quoted_activity_from_object(object)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
id: to_string(activity.id),
|
id: to_string(activity.id),
|
||||||
uri: object.data["id"],
|
uri: object.data["id"],
|
||||||
|
@ -363,6 +365,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
application: build_application(object.data["generator"]),
|
application: build_application(object.data["generator"]),
|
||||||
language: nil,
|
language: nil,
|
||||||
emojis: build_emojis(object.data["emoji"]),
|
emojis: build_emojis(object.data["emoji"]),
|
||||||
|
quote_id: (if quote, do: quote.id, else: nil),
|
||||||
|
quote: maybe_render_quote(quote, opts),
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
local: activity.local,
|
local: activity.local,
|
||||||
conversation_id: get_context_id(activity),
|
conversation_id: get_context_id(activity),
|
||||||
|
@ -604,4 +608,18 @@ defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_image_url(_, _), do: nil
|
defp build_image_url(_, _), do: nil
|
||||||
|
|
||||||
|
defp maybe_render_quote(nil, _), do: nil
|
||||||
|
|
||||||
|
defp maybe_render_quote(quote, opts) do
|
||||||
|
if opts[:do_not_recurse] do
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
opts =
|
||||||
|
opts
|
||||||
|
|> Map.put(:activity, quote)
|
||||||
|
|> Map.put(:do_not_recurse, true)
|
||||||
|
render("show.json", opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue