Quote posting #113
10 changed files with 197 additions and 0 deletions
|
@ -407,6 +407,8 @@
|
|||
accept: [],
|
||||
reject: []
|
||||
|
||||
config :pleroma, :mrf_inline_quote, prefix: "Quote"
|
||||
|
||||
# threshold of 7 days
|
||||
config :pleroma, :mrf_object_age,
|
||||
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))
|
||||
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(%{"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)
|
||||
|
|
|
@ -168,6 +168,7 @@ def note(%ActivityDraft{} = draft) do
|
|||
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
|
||||
}
|
||||
|> add_in_reply_to(draft.in_reply_to)
|
||||
|> add_quote(draft.quote)
|
||||
|> Map.merge(draft.extra)
|
||||
|
||||
{:ok, data, []}
|
||||
|
@ -183,6 +184,16 @@ defp add_in_reply_to(object, in_reply_to) do
|
|||
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
|
||||
{: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_addressing()
|
||||
|> fix_summary()
|
||||
|> fix_quote_url()
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
# we pass a fake user so that the followers collection is stripped away
|
||||
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
||||
|
|
|
@ -496,6 +496,12 @@ defp create_request do
|
|||
type: :string,
|
||||
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`."
|
||||
},
|
||||
quote_id: %Schema{
|
||||
nullable: true,
|
||||
type: :string,
|
||||
description:
|
||||
"Will quote a given status."
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
|
|
|
@ -132,6 +132,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
|||
pinned: %Schema{
|
||||
type: :boolean,
|
||||
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{
|
||||
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"},
|
||||
reblog: %Schema{
|
||||
allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
|
||||
|
|
|
@ -22,6 +22,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
attachments: [],
|
||||
in_reply_to: nil,
|
||||
in_reply_to_conversation: nil,
|
||||
quote_id: nil,
|
||||
quote: nil,
|
||||
visibility: nil,
|
||||
expires_at: nil,
|
||||
extra: nil,
|
||||
|
@ -37,6 +39,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|
|||
preview?: false,
|
||||
changes: %{}
|
||||
|
||||
|
||||
def new(user, params) do
|
||||
%__MODULE__{user: user}
|
||||
|> put_params(params)
|
||||
|
@ -54,6 +57,7 @@ def create(user, params) do
|
|||
|> with_valid(&in_reply_to/1)
|
||||
|> with_valid(&in_reply_to_conversation/1)
|
||||
|> with_valid(&visibility/1)
|
||||
|> with_valid("e_id/1)
|
||||
|> content()
|
||||
|> with_valid(&to_and_cc/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}
|
||||
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
|
||||
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
|
||||
{visibility, "direct"} when visibility != "direct" ->
|
||||
|
|
|
@ -112,6 +112,7 @@ def perform(:incoming_ap_doc, params) do
|
|||
e ->
|
||||
# Just drop those for now
|
||||
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
|
||||
IO.inspect(e)
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -329,6 +329,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
|||
|
||||
{pinned?, pinned_at} = pin_data(object, user)
|
||||
|
||||
quote = Activity.get_quoted_activity_from_object(object)
|
||||
|
||||
%{
|
||||
id: to_string(activity.id),
|
||||
uri: object.data["id"],
|
||||
|
@ -363,6 +365,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
|||
application: build_application(object.data["generator"]),
|
||||
language: nil,
|
||||
emojis: build_emojis(object.data["emoji"]),
|
||||
quote_id: (if quote, do: quote.id, else: nil),
|
||||
quote: maybe_render_quote(quote, opts),
|
||||
pleroma: %{
|
||||
local: activity.local,
|
||||
conversation_id: get_context_id(activity),
|
||||
|
@ -604,4 +608,18 @@ defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do
|
|||
end
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue