Merge branch 'features/ingestion-page' into 'develop'
Pipeline Ingestion: Page See merge request pleroma/pleroma!3097
This commit is contained in:
commit
173e977e28
|
@ -91,7 +91,7 @@ defp increase_replies_count_if_reply(%{
|
||||||
|
|
||||||
defp increase_replies_count_if_reply(_create_data), do: :noop
|
defp increase_replies_count_if_reply(_create_data), do: :noop
|
||||||
|
|
||||||
@object_types ~w[ChatMessage Question Answer Audio Video Event Article Note]
|
@object_types ~w[ChatMessage Question Answer Audio Video Event Article Note Page]
|
||||||
@impl true
|
@impl true
|
||||||
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||||
with {:ok, object} <- Object.create(object) do
|
with {:ok, object} <- Object.create(object) do
|
||||||
|
|
|
@ -20,7 +20,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||||
|
@ -102,7 +102,7 @@ def validate(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
||||||
meta
|
meta
|
||||||
)
|
)
|
||||||
when objtype in ~w[Question Answer Audio Video Event Article Note] do
|
when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
|
||||||
with {:ok, object_data} <- cast_and_apply(object),
|
with {:ok, object_data} <- cast_and_apply(object),
|
||||||
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
||||||
{:ok, create_activity} <-
|
{:ok, create_activity} <-
|
||||||
|
@ -115,15 +115,16 @@ def validate(
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate(%{"type" => type} = object, meta)
|
def validate(%{"type" => type} = object, meta)
|
||||||
when type in ~w[Event Question Audio Video Article Note] do
|
when type in ~w[Event Question Audio Video Article Note Page] do
|
||||||
validator =
|
validator =
|
||||||
case type do
|
case type do
|
||||||
"Event" -> EventValidator
|
"Event" -> EventValidator
|
||||||
"Question" -> QuestionValidator
|
"Question" -> QuestionValidator
|
||||||
"Audio" -> AudioVideoValidator
|
"Audio" -> AudioVideoValidator
|
||||||
"Video" -> AudioVideoValidator
|
"Video" -> AudioVideoValidator
|
||||||
"Article" -> ArticleNoteValidator
|
"Article" -> ArticleNotePageValidator
|
||||||
"Note" -> ArticleNoteValidator
|
"Note" -> ArticleNotePageValidator
|
||||||
|
"Page" -> ArticleNotePageValidator
|
||||||
end
|
end
|
||||||
|
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
|
@ -197,8 +198,8 @@ def cast_and_apply(%{"type" => "Event"} = object) do
|
||||||
EventValidator.cast_and_apply(object)
|
EventValidator.cast_and_apply(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note] do
|
def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do
|
||||||
ArticleNoteValidator.cast_and_apply(object)
|
ArticleNotePageValidator.cast_and_apply(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
@ -113,7 +113,7 @@ def changeset(struct, data) do
|
||||||
|
|
||||||
defp validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
data_cng
|
data_cng
|
||||||
|> validate_inclusion(:type, ["Article", "Note"])
|
|> validate_inclusion(:type, ["Article", "Note", "Page"])
|
||||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
||||||
|> CommonValidations.validate_any_presence([:cc, :to])
|
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||||
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
|
@ -437,7 +437,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_object_creation(%{"type" => objtype} = object, meta)
|
def handle_object_creation(%{"type" => objtype} = object, meta)
|
||||||
when objtype in ~w[Audio Video Question Event Article Note] do
|
when objtype in ~w[Audio Video Question Event Article Note Page] do
|
||||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
|
@ -353,29 +353,6 @@ defp get_reported(objects) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Compatibility wrapper for Mastodon votes
|
|
||||||
defp handle_create(%{"object" => %{"type" => "Answer"}} = data, _user) do
|
|
||||||
handle_incoming(data)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp handle_create(%{"object" => object} = data, user) do
|
|
||||||
%{
|
|
||||||
to: data["to"],
|
|
||||||
object: object,
|
|
||||||
actor: user,
|
|
||||||
context: object["context"],
|
|
||||||
local: false,
|
|
||||||
published: data["published"],
|
|
||||||
additional:
|
|
||||||
Map.take(data, [
|
|
||||||
"cc",
|
|
||||||
"directMessage",
|
|
||||||
"id"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|> ActivityPub.create()
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_incoming(data, options \\ [])
|
def handle_incoming(data, options \\ [])
|
||||||
|
|
||||||
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
||||||
|
@ -407,43 +384,6 @@ def handle_incoming(%{"id" => ""}, _options), do: :error
|
||||||
def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
|
def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id) < 8,
|
||||||
do: :error
|
do: :error
|
||||||
|
|
||||||
# TODO: validate those with a Ecto scheme
|
|
||||||
# - tags
|
|
||||||
# - emoji
|
|
||||||
def handle_incoming(
|
|
||||||
%{"type" => "Create", "object" => %{"type" => "Page"} = object} = data,
|
|
||||||
options
|
|
||||||
) do
|
|
||||||
actor = Containment.get_actor(data)
|
|
||||||
|
|
||||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
|
||||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor) do
|
|
||||||
data =
|
|
||||||
data
|
|
||||||
|> Map.put("object", fix_object(object, options))
|
|
||||||
|> Map.put("actor", actor)
|
|
||||||
|> fix_addressing()
|
|
||||||
|
|
||||||
with {:ok, created_activity} <- handle_create(data, user) do
|
|
||||||
reply_depth = (options[:depth] || 0) + 1
|
|
||||||
|
|
||||||
if Federator.allowed_thread_distance?(reply_depth) do
|
|
||||||
for reply_id <- replies(object) do
|
|
||||||
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
|
|
||||||
"id" => reply_id,
|
|
||||||
"depth" => reply_depth
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, created_activity}
|
|
||||||
end
|
|
||||||
else
|
|
||||||
%Activity{} = activity -> {:ok, activity}
|
|
||||||
_e -> :error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
|
%{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
|
||||||
options
|
options
|
||||||
|
@ -507,7 +447,7 @@ def handle_incoming(
|
||||||
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do
|
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do
|
||||||
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
|
17
test/fixtures/tesla_mock/lemmy-page.json
vendored
Normal file
17
test/fixtures/tesla_mock/lemmy-page.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"commentsEnabled": true,
|
||||||
|
"sensitive": false,
|
||||||
|
"stickied": false,
|
||||||
|
"attributedTo": "https://enterprise.lemmy.ml/u/nutomic",
|
||||||
|
"summary": "Hello Federation!",
|
||||||
|
"url": "https://enterprise.lemmy.ml/pictrs/image/US52d9DPvf.jpg",
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://enterprise.lemmy.ml/pictrs/image/lwFAcXHUjS.jpg"
|
||||||
|
},
|
||||||
|
"published": "2020-09-14T15:03:11.909105+00:00",
|
||||||
|
"to": "https://enterprise.lemmy.ml/c/main",
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"id": "https://enterprise.lemmy.ml/post/3",
|
||||||
|
"type": "Page"
|
||||||
|
}
|
27
test/fixtures/tesla_mock/lemmy-user.json
vendored
Normal file
27
test/fixtures/tesla_mock/lemmy-user.json
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"publicKey": {
|
||||||
|
"id": "https://enterprise.lemmy.ml/u/nutomic#main-key",
|
||||||
|
"owner": "https://enterprise.lemmy.ml/u/nutomic",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfwAYPxp1gOk2HcCRoUd\nupoecvmnpzRc5Gu6/N3YQyOyRsrYuiYLNQq2cgM3kcU80ZeEetkwkYgXkRJOKu/b\nBWb7i1zt2tdr5k6lUdW8dfCyjht8ooFPQdov8J3QYHfgBHyUYxuCNfSujryxx2wu\nLQcdjRQa5NIWcomSO8OXmCF5/Yhg2XWCbtnlxEq6Y+AFddr1mAlTOy5pBr5d+xZz\njLw/U3CioNJ79yGi/sJhgp6IyJqtUSoN3b4BgRIEts2QVvn44W1rQy9wCbRYQrO1\nBcB9Wel4k3rJJK8uHg+LpHVMaZppkNaWGkMBhMbzr8qmIlcNWNi7cbMK/p5vyviy\nSwIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
},
|
||||||
|
"inbox": "https://enterprise.lemmy.ml/u/nutomic/inbox",
|
||||||
|
"preferredUsername": "Nutomic",
|
||||||
|
"endpoints": {
|
||||||
|
"sharedInbox": "https://enterprise.lemmy.ml/inbox"
|
||||||
|
},
|
||||||
|
"summary": "some bio",
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://enterprise.lemmy.ml/pictrs/image/F6Z7QcWZRJ.jpg"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://enterprise.lemmy.ml:/pictrs/image/Q79N9oCDEG.png"
|
||||||
|
},
|
||||||
|
"published": "2020-09-14T14:54:53.080949+00:00",
|
||||||
|
"updated": "2020-10-14T10:58:28.139178+00:00",
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"id": "https://enterprise.lemmy.ml/u/nutomic",
|
||||||
|
"type": "Person",
|
||||||
|
"name": "nutomic"
|
||||||
|
}
|
|
@ -2,10 +2,10 @@
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest do
|
||||||
use Pleroma.DataCase, async: true
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "a basic note validates", %{note: note} do
|
test "a basic note validates", %{note: note} do
|
||||||
%{valid?: true} = ArticleNoteValidator.cast_and_validate(note)
|
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.Transmogrifier.PageHandlingTest do
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Object.Fetcher
|
||||||
|
|
||||||
|
test "Lemmy Page" do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{url: "https://enterprise.lemmy.ml/post/3"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
headers: [{"content-type", "application/activity+json"}],
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/lemmy-page.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
%{url: "https://enterprise.lemmy.ml/u/nutomic"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
headers: [{"content-type", "application/activity+json"}],
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/lemmy-user.json")
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, object} = Fetcher.fetch_object_from_id("https://enterprise.lemmy.ml/post/3")
|
||||||
|
|
||||||
|
assert object.data["summary"] == "Hello Federation!"
|
||||||
|
assert object.data["published"] == "2020-09-14T15:03:11.909105Z"
|
||||||
|
|
||||||
|
# WAT
|
||||||
|
assert object.data["url"] == "https://enterprise.lemmy.ml/pictrs/image/US52d9DPvf.jpg"
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue