forked from AkkomaGang/akkoma
Merge branch 'develop' into feature/expire-mutes
This commit is contained in:
commit
44e8b6037a
48 changed files with 753 additions and 428 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -9,15 +9,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
|
||||
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
|
||||
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
|
||||
|
||||
### Removed
|
||||
|
||||
- **Breaking:** `Pleroma.Workers.Cron.StatsWorker` setting from Oban `:crontab` (moved to a simpler implementation).
|
||||
- **Breaking:** `Pleroma.Workers.Cron.ClearOauthTokenWorker` setting from Oban `:crontab` (moved to scheduled jobs).
|
||||
- **Breaking:** `Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker` setting from Oban `:crontab` (moved to scheduled jobs).
|
||||
- Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were
|
||||
switched to a new configuration mechanism, however it was not officially removed until now.
|
||||
|
||||
### Changed
|
||||
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
|
||||
## unreleased-patch - ???
|
||||
|
||||
### Fixed
|
||||
|
||||
- Welcome Chat messages preventing user registration with MRF Simple Policy applied to the local instance
|
||||
- Mastodon API: the public timeline returning an error when the `reply_visibility` parameter is set to `self` for an unauthenticated user
|
||||
|
||||
## [2.1.1] - 2020-09-08
|
||||
|
||||
|
|
|
@ -216,7 +216,6 @@
|
|||
allow_relay: true,
|
||||
public: true,
|
||||
quarantined_instances: [],
|
||||
managed_config: true,
|
||||
static_dir: "instance/static/",
|
||||
allowed_post_formats: [
|
||||
"text/plain",
|
||||
|
|
|
@ -764,12 +764,6 @@
|
|||
"*.quarantined.com"
|
||||
]
|
||||
},
|
||||
%{
|
||||
key: :managed_config,
|
||||
type: :boolean,
|
||||
description:
|
||||
"Whenether the config for pleroma-fe is configured in this config or in static/config.json"
|
||||
},
|
||||
%{
|
||||
key: :static_dir,
|
||||
type: :string,
|
||||
|
|
|
@ -40,7 +40,6 @@ To add configuration to your config file, you can copy it from the base config.
|
|||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
|
||||
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note that there is a dependent setting restricting or allowing unauthenticated access to specific resources, see `restrict_unauthenticated` for more details.
|
||||
* `quarantined_instances`: List of ActivityPub instances where private (DMs, followers-only) activities will not be send.
|
||||
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
|
||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
||||
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
|
||||
older software for theses nicknames.
|
||||
|
|
|
@ -99,7 +99,7 @@ def run(["fix_likes_collections"]) do
|
|||
where: fragment("(?)->>'likes' is not null", object.data),
|
||||
select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
|
||||
)
|
||||
|> Pleroma.RepoStreamer.chunk_stream(100)
|
||||
|> Pleroma.Repo.chunk_stream(100, :batches)
|
||||
|> Stream.each(fn objects ->
|
||||
ids =
|
||||
objects
|
||||
|
@ -145,7 +145,7 @@ def run(["ensure_expiration"]) do
|
|||
|> where(local: true)
|
||||
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
||||
|> where([_a, o], fragment("?->>'type' = 'Note'", o.data))
|
||||
|> Pleroma.RepoStreamer.chunk_stream(100)
|
||||
|> Pleroma.Repo.chunk_stream(100, :batches)
|
||||
|> Stream.each(fn activities ->
|
||||
Enum.each(activities, fn activity ->
|
||||
expires_at =
|
||||
|
|
|
@ -179,7 +179,7 @@ def run(["deactivate_all_from_instance", instance]) do
|
|||
start_pleroma()
|
||||
|
||||
Pleroma.User.Query.build(%{nickname: "@#{instance}"})
|
||||
|> Pleroma.RepoStreamer.chunk_stream(500)
|
||||
|> Pleroma.Repo.chunk_stream(500, :batches)
|
||||
|> Stream.each(fn users ->
|
||||
users
|
||||
|> Enum.each(fn user ->
|
||||
|
@ -370,7 +370,7 @@ def run(["list"]) do
|
|||
start_pleroma()
|
||||
|
||||
Pleroma.User.Query.build(%{local: true})
|
||||
|> Pleroma.RepoStreamer.chunk_stream(500)
|
||||
|> Pleroma.Repo.chunk_stream(500, :batches)
|
||||
|> Stream.each(fn users ->
|
||||
users
|
||||
|> Enum.each(fn user ->
|
||||
|
|
|
@ -93,25 +93,18 @@ def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
|
|||
end)
|
||||
|
||||
{ref, state} = pop_in(state.client_monitors[client_pid])
|
||||
# DOWN message can receive right after `remove_client` call and cause worker to terminate
|
||||
state =
|
||||
if is_nil(ref) do
|
||||
state
|
||||
|
||||
Process.demonitor(ref, [:flush])
|
||||
|
||||
timer =
|
||||
if used_by == [] do
|
||||
max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
|
||||
Process.send_after(self(), :idle_close, max_idle)
|
||||
else
|
||||
Process.demonitor(ref)
|
||||
|
||||
timer =
|
||||
if used_by == [] do
|
||||
max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
|
||||
Process.send_after(self(), :idle_close, max_idle)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
%{state | timer: timer}
|
||||
nil
|
||||
end
|
||||
|
||||
{:reply, :ok, state, :hibernate}
|
||||
{:reply, :ok, %{state | timer: timer}, :hibernate}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
|
@ -19,13 +19,13 @@ def fill_in_notification_types do
|
|||
query
|
||||
|> Repo.chunk_stream(100)
|
||||
|> Enum.each(fn notification ->
|
||||
type =
|
||||
notification.activity
|
||||
|> type_from_activity()
|
||||
if notification.activity do
|
||||
type = type_from_activity(notification.activity)
|
||||
|
||||
notification
|
||||
|> Ecto.Changeset.change(%{type: type})
|
||||
|> Repo.update()
|
||||
notification
|
||||
|> Ecto.Changeset.change(%{type: type})
|
||||
|> Repo.update()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -72,8 +72,7 @@ defp type_from_activity(%{data: %{"type" => type}} = activity) do
|
|||
"pleroma:emoji_reaction"
|
||||
|
||||
"Create" ->
|
||||
activity
|
||||
|> type_from_activity_object()
|
||||
type_from_activity_object(activity)
|
||||
|
||||
t ->
|
||||
raise "No notification type for activity type #{t}"
|
||||
|
|
|
@ -98,8 +98,8 @@ def fetch_object_from_id(id, options \\ []) do
|
|||
{:containment, _} ->
|
||||
{:error, "Object containment failed."}
|
||||
|
||||
{:transmogrifier, {:error, {:reject, nil}}} ->
|
||||
{:reject, nil}
|
||||
{:transmogrifier, {:error, {:reject, e}}} ->
|
||||
{:reject, e}
|
||||
|
||||
{:transmogrifier, _} = e ->
|
||||
{:error, e}
|
||||
|
|
|
@ -49,7 +49,21 @@ def get_assoc(resource, association) do
|
|||
end
|
||||
end
|
||||
|
||||
def chunk_stream(query, chunk_size) do
|
||||
@doc """
|
||||
Returns a lazy enumerable that emits all entries from the data store matching the given query.
|
||||
|
||||
`returns_as` use to group records. use the `batches` option to fetch records in bulk.
|
||||
|
||||
## Examples
|
||||
|
||||
# fetch records one-by-one
|
||||
iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500)
|
||||
|
||||
# fetch records in bulk
|
||||
iex> Pleroma.Repo.chunk_stream(Pleroma.Activity.Queries.by_actor(ap_id), 500, :batches)
|
||||
"""
|
||||
@spec chunk_stream(Ecto.Query.t(), integer(), atom()) :: Enumerable.t()
|
||||
def chunk_stream(query, chunk_size, returns_as \\ :one) do
|
||||
# We don't actually need start and end funcitons of resource streaming,
|
||||
# but it seems to be the only way to not fetch records one-by-one and
|
||||
# have individual records be the elements of the stream, instead of
|
||||
|
@ -69,7 +83,12 @@ def chunk_stream(query, chunk_size) do
|
|||
|
||||
records ->
|
||||
last_id = List.last(records).id
|
||||
{records, last_id}
|
||||
|
||||
if returns_as == :one do
|
||||
{records, last_id}
|
||||
else
|
||||
{[records], last_id}
|
||||
end
|
||||
end
|
||||
end,
|
||||
fn _ -> :ok end
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.RepoStreamer do
|
||||
alias Pleroma.Repo
|
||||
import Ecto.Query
|
||||
|
||||
def chunk_stream(query, chunk_size) do
|
||||
Stream.unfold(0, fn
|
||||
:halt ->
|
||||
{[], :halt}
|
||||
|
||||
last_id ->
|
||||
query
|
||||
|> order_by(asc: :id)
|
||||
|> where([r], r.id > ^last_id)
|
||||
|> limit(^chunk_size)
|
||||
|> Repo.all()
|
||||
|> case do
|
||||
[] ->
|
||||
{[], :halt}
|
||||
|
||||
records ->
|
||||
last_id = List.last(records).id
|
||||
{records, last_id}
|
||||
end
|
||||
end)
|
||||
|> Stream.take_while(fn
|
||||
[] -> false
|
||||
_ -> true
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -25,7 +25,6 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Object
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.RepoStreamer
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserRelationship
|
||||
alias Pleroma.Web
|
||||
|
@ -276,9 +275,9 @@ def binary_id(%User{} = user), do: binary_id(user.id)
|
|||
@spec account_status(User.t()) :: account_status()
|
||||
def account_status(%User{deactivated: true}), do: :deactivated
|
||||
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
|
||||
def account_status(%User{approval_pending: true}), do: :approval_pending
|
||||
def account_status(%User{local: true, approval_pending: true}), do: :approval_pending
|
||||
|
||||
def account_status(%User{confirmation_pending: true}) do
|
||||
def account_status(%User{local: true, confirmation_pending: true}) do
|
||||
if Config.get([:instance, :account_activation_required]) do
|
||||
:confirmation_pending
|
||||
else
|
||||
|
@ -1795,7 +1794,7 @@ def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
|
|||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||
ap_id
|
||||
|> Activity.Queries.by_actor()
|
||||
|> RepoStreamer.chunk_stream(50)
|
||||
|> Repo.chunk_stream(50, :batches)
|
||||
|> Stream.each(fn activities ->
|
||||
Enum.each(activities, fn activity -> delete_activity(activity, user) end)
|
||||
end)
|
||||
|
|
|
@ -84,7 +84,7 @@ defp increase_replies_count_if_reply(%{
|
|||
|
||||
defp increase_replies_count_if_reply(_create_data), do: :noop
|
||||
|
||||
@object_types ~w[ChatMessage Question Answer Audio Event]
|
||||
@object_types ~w[ChatMessage Question Answer Audio Video Event Article]
|
||||
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
|
||||
def persist(%{"type" => type} = object, meta) when type in @object_types do
|
||||
with {:ok, object} <- Object.create(object) do
|
||||
|
@ -154,8 +154,8 @@ def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when
|
|||
{:remote_limit_pass, _} ->
|
||||
{:error, :remote_limit}
|
||||
|
||||
{:reject, reason} ->
|
||||
{:error, reason}
|
||||
{:reject, _} = e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -767,7 +767,7 @@ defp restrict_replies(query, %{exclude_replies: true}) do
|
|||
end
|
||||
|
||||
defp restrict_replies(query, %{
|
||||
reply_filtering_user: user,
|
||||
reply_filtering_user: %User{} = user,
|
||||
reply_visibility: "self"
|
||||
}) do
|
||||
from(
|
||||
|
@ -783,7 +783,7 @@ defp restrict_replies(query, %{
|
|||
end
|
||||
|
||||
defp restrict_replies(query, %{
|
||||
reply_filtering_user: user,
|
||||
reply_filtering_user: %User{} = user,
|
||||
reply_visibility: "following"
|
||||
}) do
|
||||
from(
|
||||
|
|
|
@ -66,7 +66,8 @@ defp check_media_nsfw(
|
|||
"type" => "Create",
|
||||
"object" => child_object
|
||||
} = object
|
||||
) do
|
||||
)
|
||||
when is_map(child_object) do
|
||||
media_nsfw =
|
||||
Config.get([:mrf_simple, :media_nsfw])
|
||||
|> MRF.subdomains_regex()
|
||||
|
|
|
@ -12,11 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||
|
@ -149,10 +151,20 @@ def validate(%{"type" => "Question"} = object, meta) do
|
|||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Audio"} = object, meta) do
|
||||
def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> AudioValidator.cast_and_validate()
|
||||
|> AudioVideoValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => "Article"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> ArticleNoteValidator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
|
@ -198,7 +210,7 @@ def validate(
|
|||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
|
||||
meta
|
||||
)
|
||||
when objtype in ~w[Question Answer Audio Event] do
|
||||
when objtype in ~w[Question Answer Audio Video Event Article] do
|
||||
with {:ok, object_data} <- cast_and_apply(object),
|
||||
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
|
||||
{:ok, create_activity} <-
|
||||
|
@ -232,14 +244,18 @@ def cast_and_apply(%{"type" => "Answer"} = object) do
|
|||
AnswerValidator.cast_and_apply(object)
|
||||
end
|
||||
|
||||
def cast_and_apply(%{"type" => "Audio"} = object) do
|
||||
AudioValidator.cast_and_apply(object)
|
||||
def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do
|
||||
AudioVideoValidator.cast_and_apply(object)
|
||||
end
|
||||
|
||||
def cast_and_apply(%{"type" => "Event"} = object) do
|
||||
EventValidator.cast_and_apply(object)
|
||||
end
|
||||
|
||||
def cast_and_apply(%{"type" => "Article"} = object) do
|
||||
ArticleNoteValidator.cast_and_apply(object)
|
||||
end
|
||||
|
||||
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
|
||||
|
||||
# is_struct/1 isn't present in Elixir 1.8.x
|
||||
|
@ -262,7 +278,8 @@ def stringify_keys(object) when is_list(object) do
|
|||
def stringify_keys(object), do: object
|
||||
|
||||
def fetch_actor(object) do
|
||||
with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do
|
||||
with actor <- Containment.get_actor(object),
|
||||
{:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
|
||||
User.get_or_fetch_by_ap_id(actor)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
@ -25,14 +25,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
|
|||
# TODO: Write type
|
||||
field(:tag, {:array, :map}, default: [])
|
||||
field(:type, :string)
|
||||
|
||||
field(:name, :string)
|
||||
field(:summary, :string)
|
||||
field(:content, :string)
|
||||
|
||||
field(:context, :string)
|
||||
# short identifier for PleromaFE to group statuses by context
|
||||
field(:context_id, :integer)
|
||||
|
||||
# TODO: Remove actor on objects
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
|
||||
field(:attributedTo, ObjectValidators.ObjectID)
|
||||
field(:summary, :string)
|
||||
field(:published, ObjectValidators.DateTime)
|
||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
||||
field(:sensitive, :boolean, default: false)
|
||||
|
@ -40,13 +45,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
|
|||
field(:replies_count, :integer, default: 0)
|
||||
field(:like_count, :integer, default: 0)
|
||||
field(:announcement_count, :integer, default: 0)
|
||||
field(:inReplyTo, :string)
|
||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||
field(:url, ObjectValidators.Uri)
|
||||
# short identifier for PleromaFE to group statuses by context
|
||||
field(:context_id, :integer)
|
||||
|
||||
field(:likes, {:array, :string}, default: [])
|
||||
field(:announcements, {:array, :string}, default: [])
|
||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
end
|
||||
|
||||
def cast_and_apply(data) do
|
||||
|
@ -62,19 +65,14 @@ def cast_and_validate(data) do
|
|||
end
|
||||
|
||||
def cast_data(data) do
|
||||
data = fix(data)
|
||||
|
||||
%__MODULE__{}
|
||||
|> changeset(data)
|
||||
end
|
||||
|
||||
defp fix_url(%{"url" => url} = data) when is_list(url) do
|
||||
attachment =
|
||||
Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end)
|
||||
|
||||
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
|
||||
|
||||
data
|
||||
|> Map.put("attachment", [attachment])
|
||||
|> Map.put("url", link_element["href"])
|
||||
defp fix_url(%{"url" => url} = data) when is_map(url) do
|
||||
Map.put(data, "url", url["href"])
|
||||
end
|
||||
|
||||
defp fix_url(data), do: data
|
||||
|
@ -83,8 +81,9 @@ defp fix(data) do
|
|||
data
|
||||
|> CommonFixes.fix_defaults()
|
||||
|> CommonFixes.fix_attribution()
|
||||
|> Transmogrifier.fix_emoji()
|
||||
|> CommonFixes.fix_actor()
|
||||
|> fix_url()
|
||||
|> Transmogrifier.fix_emoji()
|
||||
end
|
||||
|
||||
def changeset(struct, data) do
|
||||
|
@ -97,8 +96,8 @@ def changeset(struct, data) do
|
|||
|
||||
def validate_data(data_cng) do
|
||||
data_cng
|
||||
|> validate_inclusion(:type, ["Audio"])
|
||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
|
||||
|> validate_inclusion(:type, ["Article", "Note"])
|
||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|
||||
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
||||
|> CommonValidations.validate_actor_presence()
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
|
||||
|
||||
import Ecto.Changeset
|
||||
|
@ -15,7 +16,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
|
|||
field(:mediaType, :string, default: "application/octet-stream")
|
||||
field(:name, :string)
|
||||
|
||||
embeds_many(:url, UrlObjectValidator)
|
||||
embeds_many :url, UrlObjectValidator, primary_key: false do
|
||||
field(:type, :string)
|
||||
field(:href, ObjectValidators.Uri)
|
||||
field(:mediaType, :string, default: "application/octet-stream")
|
||||
end
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
|
@ -37,7 +42,18 @@ def changeset(struct, data) do
|
|||
|
||||
struct
|
||||
|> cast(data, [:type, :mediaType, :name])
|
||||
|> cast_embed(:url, required: true)
|
||||
|> cast_embed(:url, with: &url_changeset/2)
|
||||
|> validate_inclusion(:type, ~w[Link Document Audio Image Video])
|
||||
|> validate_required([:type, :mediaType, :url])
|
||||
end
|
||||
|
||||
def url_changeset(struct, data) do
|
||||
data = fix_media_type(data)
|
||||
|
||||
struct
|
||||
|> cast(data, [:type, :href, :mediaType])
|
||||
|> validate_inclusion(:type, ["Link"])
|
||||
|> validate_required([:type, :href, :mediaType])
|
||||
end
|
||||
|
||||
def fix_media_type(data) do
|
||||
|
@ -75,6 +91,7 @@ defp fix_url(data) do
|
|||
|
||||
def validate_data(cng) do
|
||||
cng
|
||||
|> validate_inclusion(:type, ~w[Document Audio Image Video])
|
||||
|> validate_required([:mediaType, :url, :type])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
# 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.ObjectValidators.AudioVideoValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EarmarkRenderer
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
@derive Jason.Encoder
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:bto, ObjectValidators.Recipients, default: [])
|
||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||
# TODO: Write type
|
||||
field(:tag, {:array, :map}, default: [])
|
||||
field(:type, :string)
|
||||
|
||||
field(:name, :string)
|
||||
field(:summary, :string)
|
||||
field(:content, :string)
|
||||
|
||||
field(:context, :string)
|
||||
# short identifier for PleromaFE to group statuses by context
|
||||
field(:context_id, :integer)
|
||||
|
||||
# TODO: Remove actor on objects
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
|
||||
field(:attributedTo, ObjectValidators.ObjectID)
|
||||
field(:published, ObjectValidators.DateTime)
|
||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
||||
field(:sensitive, :boolean, default: false)
|
||||
embeds_many(:attachment, AttachmentValidator)
|
||||
field(:replies_count, :integer, default: 0)
|
||||
field(:like_count, :integer, default: 0)
|
||||
field(:announcement_count, :integer, default: 0)
|
||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||
field(:url, ObjectValidators.Uri)
|
||||
|
||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
end
|
||||
|
||||
def cast_and_apply(data) do
|
||||
data
|
||||
|> cast_data
|
||||
|> apply_action(:insert)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data()
|
||||
|> validate_data()
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> changeset(data)
|
||||
end
|
||||
|
||||
defp fix_url(%{"url" => url} = data) when is_list(url) do
|
||||
attachment =
|
||||
Enum.find(url, fn x ->
|
||||
mime_type = x["mimeType"] || x["mediaType"] || ""
|
||||
|
||||
is_map(x) and String.starts_with?(mime_type, ["video/", "audio/"])
|
||||
end)
|
||||
|
||||
link_element =
|
||||
Enum.find(url, fn x ->
|
||||
mime_type = x["mimeType"] || x["mediaType"] || ""
|
||||
|
||||
is_map(x) and mime_type == "text/html"
|
||||
end)
|
||||
|
||||
data
|
||||
|> Map.put("attachment", [attachment])
|
||||
|> Map.put("url", link_element["href"])
|
||||
end
|
||||
|
||||
defp fix_url(data), do: data
|
||||
|
||||
defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
|
||||
when is_binary(content) do
|
||||
content =
|
||||
content
|
||||
|> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
|
||||
|> Pleroma.HTML.filter_tags()
|
||||
|
||||
Map.put(data, "content", content)
|
||||
end
|
||||
|
||||
defp fix_content(data), do: data
|
||||
|
||||
defp fix(data) do
|
||||
data
|
||||
|> CommonFixes.fix_defaults()
|
||||
|> CommonFixes.fix_attribution()
|
||||
|> CommonFixes.fix_actor()
|
||||
|> Transmogrifier.fix_emoji()
|
||||
|> fix_url()
|
||||
|> fix_content()
|
||||
end
|
||||
|
||||
def changeset(struct, data) do
|
||||
data = fix(data)
|
||||
|
||||
struct
|
||||
|> cast(data, __schema__(:fields) -- [:attachment])
|
||||
|> cast_embed(:attachment)
|
||||
end
|
||||
|
||||
def validate_data(data_cng) do
|
||||
data_cng
|
||||
|> validate_inclusion(:type, ["Audio", "Video"])
|
||||
|> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
|
||||
|> CommonValidations.validate_any_presence([:cc, :to])
|
||||
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|
||||
|> CommonValidations.validate_actor_presence()
|
||||
|> CommonValidations.validate_host_match()
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
# based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
|
||||
|
@ -19,4 +20,12 @@ def fix_attribution(data) do
|
|||
data
|
||||
|> Map.put_new("actor", data["attributedTo"])
|
||||
end
|
||||
|
||||
def fix_actor(data) do
|
||||
actor = Containment.get_actor(data)
|
||||
|
||||
data
|
||||
|> Map.put("actor", actor)
|
||||
|> Map.put("attributedTo", actor)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,9 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
|
|||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
|
@ -75,14 +76,15 @@ defp fix(data, meta) do
|
|||
data
|
||||
|> fix_context(meta)
|
||||
|> fix_addressing(meta)
|
||||
|> CommonFixes.fix_actor()
|
||||
end
|
||||
|
||||
def validate_data(cng, meta \\ []) do
|
||||
cng
|
||||
|> validate_required([:actor, :type, :object])
|
||||
|> validate_inclusion(:type, ["Create"])
|
||||
|> validate_actor_presence()
|
||||
|> validate_any_presence([:to, :cc])
|
||||
|> CommonValidations.validate_actor_presence()
|
||||
|> CommonValidations.validate_any_presence([:to, :cc])
|
||||
|> validate_actors_match(meta)
|
||||
|> validate_context_match(meta)
|
||||
|> validate_object_nonexistence()
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
# 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.ObjectValidators.NoteValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||
field(:to, ObjectValidators.Recipients, default: [])
|
||||
field(:cc, ObjectValidators.Recipients, default: [])
|
||||
field(:bto, ObjectValidators.Recipients, default: [])
|
||||
field(:bcc, ObjectValidators.Recipients, default: [])
|
||||
# TODO: Write type
|
||||
field(:tag, {:array, :map}, default: [])
|
||||
field(:type, :string)
|
||||
|
||||
field(:name, :string)
|
||||
field(:summary, :string)
|
||||
field(:content, :string)
|
||||
|
||||
field(:context, :string)
|
||||
# short identifier for PleromaFE to group statuses by context
|
||||
field(:context_id, :integer)
|
||||
|
||||
field(:actor, ObjectValidators.ObjectID)
|
||||
field(:attributedTo, ObjectValidators.ObjectID)
|
||||
field(:published, ObjectValidators.DateTime)
|
||||
field(:emoji, ObjectValidators.Emoji, default: %{})
|
||||
field(:sensitive, :boolean, default: false)
|
||||
# TODO: Write type
|
||||
field(:attachment, {:array, :map}, default: [])
|
||||
field(:replies_count, :integer, default: 0)
|
||||
field(:like_count, :integer, default: 0)
|
||||
field(:announcement_count, :integer, default: 0)
|
||||
field(:inReplyTo, ObjectValidators.ObjectID)
|
||||
field(:url, ObjectValidators.Uri)
|
||||
|
||||
field(:likes, {:array, :string}, default: [])
|
||||
field(:announcements, {:array, :string}, default: [])
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data()
|
||||
|> validate_data()
|
||||
end
|
||||
|
||||
defp fix(data) do
|
||||
data
|
||||
|> Transmogrifier.fix_emoji()
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
data = fix(data)
|
||||
|
||||
%__MODULE__{}
|
||||
|> cast(data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def validate_data(data_cng) do
|
||||
data_cng
|
||||
|> validate_inclusion(:type, ["Note"])
|
||||
|> validate_required([:id, :actor, :to, :cc, :type, :content, :context])
|
||||
end
|
||||
end
|
|
@ -47,8 +47,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
|||
# short identifier for PleromaFE to group statuses by context
|
||||
field(:context_id, :integer)
|
||||
|
||||
field(:likes, {:array, :string}, default: [])
|
||||
field(:announcements, {:array, :string}, default: [])
|
||||
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
|
||||
field(:closed, ObjectValidators.DateTime)
|
||||
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# 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.ObjectValidators.UrlObjectValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
||||
import Ecto.Changeset
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:type, :string)
|
||||
field(:href, ObjectValidators.Uri)
|
||||
field(:mediaType, :string, default: "application/octet-stream")
|
||||
end
|
||||
|
||||
def changeset(struct, data) do
|
||||
struct
|
||||
|> cast(data, __schema__(:fields))
|
||||
|> validate_required([:type, :href, :mediaType])
|
||||
end
|
||||
end
|
|
@ -336,7 +336,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
|
|||
end
|
||||
|
||||
def handle_object_creation(%{"type" => objtype} = object, meta)
|
||||
when objtype in ~w[Audio Question Event] do
|
||||
when objtype in ~w[Audio Video Question Event Article] do
|
||||
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
A module to handle coding from internal to wire ActivityPub and back.
|
||||
"""
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EarmarkRenderer
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Object
|
||||
|
@ -45,7 +44,6 @@ def fix_object(object, options \\ []) do
|
|||
|> fix_addressing
|
||||
|> fix_summary
|
||||
|> fix_type(options)
|
||||
|> fix_content
|
||||
end
|
||||
|
||||
def fix_summary(%{"summary" => nil} = object) do
|
||||
|
@ -274,24 +272,7 @@ def fix_url(%{"url" => url} = object) when is_map(url) do
|
|||
Map.put(object, "url", url["href"])
|
||||
end
|
||||
|
||||
def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
|
||||
attachment =
|
||||
Enum.find(url, fn x ->
|
||||
media_type = x["mediaType"] || x["mimeType"] || ""
|
||||
|
||||
is_map(x) and String.starts_with?(media_type, "video/")
|
||||
end)
|
||||
|
||||
link_element =
|
||||
Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
|
||||
|
||||
object
|
||||
|> Map.put("attachment", [attachment])
|
||||
|> Map.put("url", link_element["href"])
|
||||
end
|
||||
|
||||
def fix_url(%{"type" => object_type, "url" => url} = object)
|
||||
when object_type != "Video" and is_list(url) do
|
||||
def fix_url(%{"url" => url} = object) when is_list(url) do
|
||||
first_element = Enum.at(url, 0)
|
||||
|
||||
url_string =
|
||||
|
@ -309,7 +290,7 @@ def fix_url(object), do: object
|
|||
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
|
||||
emoji =
|
||||
tags
|
||||
|> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|
||||
|> Enum.filter(fn data -> is_map(data) and data["type"] == "Emoji" and data["icon"] end)
|
||||
|> Enum.reduce(%{}, fn data, mapping ->
|
||||
name = String.trim(data["name"], ":")
|
||||
|
||||
|
@ -371,18 +352,6 @@ def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
|
|||
|
||||
def fix_type(object, _), do: object
|
||||
|
||||
defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object)
|
||||
when is_binary(content) do
|
||||
html_content =
|
||||
content
|
||||
|> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
|
||||
|> Pleroma.HTML.filter_tags()
|
||||
|
||||
Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"})
|
||||
end
|
||||
|
||||
defp fix_content(object), do: object
|
||||
|
||||
# Reduce the object list to find the reported user.
|
||||
defp get_reported(objects) do
|
||||
Enum.reduce_while(objects, nil, fn ap_id, _ ->
|
||||
|
@ -455,7 +424,7 @@ def handle_incoming(
|
|||
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
|
||||
options
|
||||
)
|
||||
when objtype in ~w{Article Note Video Page} do
|
||||
when objtype in ~w{Note Page} do
|
||||
actor = Containment.get_actor(data)
|
||||
|
||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||
|
@ -549,7 +518,9 @@ def handle_incoming(
|
|||
%{"type" => "Create", "object" => %{"type" => objtype}} = data,
|
||||
_options
|
||||
)
|
||||
when objtype in ~w{Question Answer ChatMessage Audio Event} do
|
||||
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
|
||||
data = Map.put(data, "object", strip_internal_fields(data["object"]))
|
||||
|
||||
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||
{:ok, activity}
|
||||
|
|
|
@ -87,6 +87,50 @@ def perform(:fetch, %Activity{} = activity) do
|
|||
def rich_media_get(url) do
|
||||
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
|
||||
Pleroma.HTTP.get(url, headers, @options)
|
||||
head_check =
|
||||
case Pleroma.HTTP.head(url, headers, @options) do
|
||||
# If the HEAD request didn't reach the server for whatever reason,
|
||||
# we assume the GET that comes right after won't either
|
||||
{:error, _} = e ->
|
||||
e
|
||||
|
||||
{:ok, %Tesla.Env{status: 200, headers: headers}} ->
|
||||
with :ok <- check_content_type(headers),
|
||||
:ok <- check_content_length(headers),
|
||||
do: :ok
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options)
|
||||
end
|
||||
|
||||
defp check_content_type(headers) do
|
||||
case List.keyfind(headers, "content-type", 0) do
|
||||
{_, content_type} ->
|
||||
case Plug.Conn.Utils.media_type(content_type) do
|
||||
{:ok, "text", "html", _} -> :ok
|
||||
_ -> {:error, {:content_type, content_type}}
|
||||
end
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@max_body @options[:max_body]
|
||||
defp check_content_length(headers) do
|
||||
case List.keyfind(headers, "content-length", 0) do
|
||||
{_, maybe_content_length} ->
|
||||
case Integer.parse(maybe_content_length) do
|
||||
{content_length, ""} when content_length <= @max_body -> :ok
|
||||
{_, ""} -> {:error, :body_too_large}
|
||||
_ -> :ok
|
||||
end
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,6 +36,14 @@ defp get_cached_or_parse(url) do
|
|||
{:ok, _data} = res ->
|
||||
res
|
||||
|
||||
{:error, :body_too_large} = e ->
|
||||
e
|
||||
|
||||
{:error, {:content_type, _}} = e ->
|
||||
e
|
||||
|
||||
# The TTL is not set for the errors above, since they are unlikely to change
|
||||
# with time
|
||||
{:error, _} = e ->
|
||||
ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
|
||||
Cachex.expire(:rich_media_cache, url, ttl)
|
||||
|
|
|
@ -4,6 +4,8 @@ defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do
|
|||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
def change do
|
||||
Pleroma.Config.Oban.warn()
|
||||
|
||||
Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}],
|
||||
strategy: :one_for_one,
|
||||
name: Pleroma.Supervisor
|
||||
|
|
|
@ -4,6 +4,8 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do
|
|||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
def change do
|
||||
Pleroma.Config.Oban.warn()
|
||||
|
||||
Supervisor.start_link([{Oban, Pleroma.Config.get(Oban)}],
|
||||
strategy: :one_for_one,
|
||||
name: Pleroma.Supervisor
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
defmodule Pleroma.Repo.Migrations.RemoveManagedConfigFromDb do
|
||||
use Ecto.Migration
|
||||
import Ecto.Query
|
||||
alias Pleroma.ConfigDB
|
||||
alias Pleroma.Repo
|
||||
|
||||
def up do
|
||||
config_entry =
|
||||
from(c in ConfigDB,
|
||||
select: [:id, :value],
|
||||
where: c.group == ^:pleroma and c.key == ^:instance
|
||||
)
|
||||
|> Repo.one()
|
||||
|
||||
if config_entry do
|
||||
{_, value} = Keyword.pop(config_entry.value, :managed_config)
|
||||
|
||||
config_entry
|
||||
|> Ecto.Changeset.change(value: value)
|
||||
|> Repo.update()
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
defmodule Pleroma.Repo.Migrations.DeleteNotificationWithoutActivity do
|
||||
use Ecto.Migration
|
||||
|
||||
import Ecto.Query
|
||||
alias Pleroma.Repo
|
||||
|
||||
def up do
|
||||
from(
|
||||
q in Pleroma.Notification,
|
||||
left_join: c in assoc(q, :activity),
|
||||
select: %{id: type(q.id, :integer)},
|
||||
where: is_nil(c.id)
|
||||
)
|
||||
|> Repo.chunk_stream(1_000, :batches)
|
||||
|> Stream.each(fn records ->
|
||||
notification_ids = Enum.map(records, fn %{id: id} -> id end)
|
||||
|
||||
Repo.delete_all(
|
||||
from(n in "notifications",
|
||||
where: n.id in ^notification_ids
|
||||
)
|
||||
)
|
||||
end)
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
def down do
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddNotificationConstraints do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
drop(constraint(:notifications, "notifications_activity_id_fkey"))
|
||||
|
||||
alter table(:notifications) do
|
||||
modify(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all),
|
||||
null: false
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
drop(constraint(:notifications, "notifications_activity_id_fkey"))
|
||||
|
||||
alter table(:notifications) do
|
||||
modify(:activity_id, references(:activities, type: :uuid, on_delete: :delete_all),
|
||||
null: true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because one or more lines are too long
1
test/fixtures/tesla_mock/wedistribute-create-article.json
vendored
Normal file
1
test/fixtures/tesla_mock/wedistribute-create-article.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{"@context":["https:\/\/www.w3.org\/ns\/activitystreams"],"type":"Create","actor":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog","object":{"@context":["https:\/\/www.w3.org\/ns\/activitystreams"],"type":"Article","name":"The end is near: Mastodon plans to drop OStatus support","content":"<!-- wp:paragraph {\"dropCap\":true} -->\n<p class=\"has-drop-cap\">The days of OStatus are numbered. The venerable protocol has served as a glue between many different types of servers since the early days of the Fediverse, connecting StatusNet (now GNU Social) to Friendica, Hubzilla, Mastodon, and Pleroma.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Now that many fediverse platforms support ActivityPub as a successor protocol, Mastodon appears to be drawing a line in the sand. In <a href=\"https:\/\/www.patreon.com\/posts\/mastodon-2-9-and-28121681\">a Patreon update<\/a>, Eugen Rochko writes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:quote -->\n<blockquote class=\"wp-block-quote\"><p>...OStatus...has overstayed its welcome in the code...and now that most of the network uses ActivityPub, it's time for it to go. <\/p><cite>Eugen Rochko, Mastodon creator<\/cite><\/blockquote>\n<!-- \/wp:quote -->\n\n<!-- wp:paragraph -->\n<p>The <a href=\"https:\/\/github.com\/tootsuite\/mastodon\/pull\/11205\">pull request<\/a> to remove Pubsubhubbub and Salmon, two of the main components of OStatus, has already been merged into Mastodon's master branch.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Some projects will be left in the dark as a side effect of this. GNU Social and PostActiv, for example, both only communicate using OStatus. While <a href=\"https:\/\/mastodon.social\/@dansup\/102076573310057902\">some discussion<\/a> exists regarding adopting ActivityPub for GNU Social, and <a href=\"https:\/\/notabug.org\/diogo\/gnu-social\/src\/activitypub\/plugins\/ActivityPub\">a plugin is in development<\/a>, it hasn't been formally adopted yet. We just hope that the <a href=\"https:\/\/status.fsf.org\/main\/public\">Free Software Foundation's instance<\/a> gets updated in time!<\/p>\n<!-- \/wp:paragraph -->","summary":"One of the largest platforms in the federated social web is dropping the protocol that it started with.","attributedTo":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog","url":"https:\/\/wedistribute.org\/2019\/07\/mastodon-drops-ostatus\/","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public","https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog\/followers"],"id":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810","likes":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810\/likes","shares":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85810\/shares"},"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public","https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/actor\/-blog\/followers"],"id":"https:\/\/wedistribute.org\/wp-json\/pterotype\/v1\/object\/85809"}
|
|
@ -33,8 +33,8 @@ test "return empty multi" do
|
|||
test "returns user markers" do
|
||||
user = insert(:user)
|
||||
marker = insert(:marker, user: user)
|
||||
insert(:notification, user: user)
|
||||
insert(:notification, user: user)
|
||||
insert(:notification, user: user, activity: insert(:note_activity))
|
||||
insert(:notification, user: user, activity: insert(:note_activity))
|
||||
insert(:marker, timeline: "home", user: user)
|
||||
|
||||
assert Marker.get_markers(
|
||||
|
|
|
@ -6,10 +6,12 @@ defmodule Pleroma.Object.FetcherTest do
|
|||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
import Tesla.Mock
|
||||
|
||||
import Mock
|
||||
import Tesla.Mock
|
||||
|
||||
setup do
|
||||
mock(fn
|
||||
|
@ -71,20 +73,20 @@ test "it works when fetching the OP actor errors out" do
|
|||
setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
|
||||
|
||||
test "it returns thread depth exceeded error if thread depth is exceeded" do
|
||||
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
|
||||
Config.put([:instance, :federation_incoming_replies_max_depth], 0)
|
||||
|
||||
assert {:error, "Max thread distance exceeded."} =
|
||||
Fetcher.fetch_object_from_id(@ap_id, depth: 1)
|
||||
end
|
||||
|
||||
test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do
|
||||
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
|
||||
Config.put([:instance, :federation_incoming_replies_max_depth], 0)
|
||||
|
||||
assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id)
|
||||
end
|
||||
|
||||
test "it fetches object if requested depth does not exceed max thread depth" do
|
||||
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10)
|
||||
Config.put([:instance, :federation_incoming_replies_max_depth], 10)
|
||||
|
||||
assert {:ok, _} = Fetcher.fetch_object_from_id(@ap_id, depth: 10)
|
||||
end
|
||||
|
@ -120,6 +122,16 @@ test "it fetches an object" do
|
|||
|
||||
assert object == object_again
|
||||
end
|
||||
|
||||
test "Return MRF reason when fetched status is rejected by one" do
|
||||
clear_config([:mrf_keyword, :reject], ["yeah"])
|
||||
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
|
||||
|
||||
assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
|
||||
Fetcher.fetch_object_from_id(
|
||||
"http://mastodon.example.org/@admin/99541947525187367"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "implementation quirks" do
|
||||
|
@ -212,7 +224,7 @@ test "it can refetch pruned objects" do
|
|||
Pleroma.Signature,
|
||||
[:passthrough],
|
||||
[] do
|
||||
Pleroma.Config.put([:activitypub, :sign_object_fetches], true)
|
||||
Config.put([:activitypub, :sign_object_fetches], true)
|
||||
|
||||
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
|
||||
|
@ -223,7 +235,7 @@ test "it can refetch pruned objects" do
|
|||
Pleroma.Signature,
|
||||
[:passthrough],
|
||||
[] do
|
||||
Pleroma.Config.put([:activitypub, :sign_object_fetches], false)
|
||||
Config.put([:activitypub, :sign_object_fetches], false)
|
||||
|
||||
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
|
||||
|
|
|
@ -37,7 +37,9 @@ test "get one-to-one assoc from repo" do
|
|||
|
||||
test "get one-to-many assoc from repo" do
|
||||
user = insert(:user)
|
||||
notification = refresh_record(insert(:notification, user: user))
|
||||
|
||||
notification =
|
||||
refresh_record(insert(:notification, user: user, activity: insert(:note_activity)))
|
||||
|
||||
assert Repo.get_assoc(user, :notifications) == {:ok, [notification]}
|
||||
end
|
||||
|
@ -47,4 +49,32 @@ test "return error if has not assoc " do
|
|||
assert Repo.get_assoc(token, :user) == {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
describe "chunk_stream/3" do
|
||||
test "fetch records one-by-one" do
|
||||
users = insert_list(50, :user)
|
||||
|
||||
{fetch_users, 50} =
|
||||
from(t in User)
|
||||
|> Repo.chunk_stream(5)
|
||||
|> Enum.reduce({[], 0}, fn %User{} = user, {acc, count} ->
|
||||
{acc ++ [user], count + 1}
|
||||
end)
|
||||
|
||||
assert users == fetch_users
|
||||
end
|
||||
|
||||
test "fetch records in bulk" do
|
||||
users = insert_list(50, :user)
|
||||
|
||||
{fetch_users, 10} =
|
||||
from(t in User)
|
||||
|> Repo.chunk_stream(5, :batches)
|
||||
|> Enum.reduce({[], 0}, fn users, {acc, count} ->
|
||||
{acc ++ users, count + 1}
|
||||
end)
|
||||
|
||||
assert users == fetch_users
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1262,4 +1262,21 @@ def post(url, query, body, headers) do
|
|||
inspect(headers)
|
||||
}"}
|
||||
end
|
||||
|
||||
# Most of the rich media mocks are missing HEAD requests, so we just return 404.
|
||||
@rich_media_mocks [
|
||||
"https://example.com/ogp",
|
||||
"https://example.com/ogp-missing-data",
|
||||
"https://example.com/twitter-card"
|
||||
]
|
||||
def head(url, _query, _body, _headers) when url in @rich_media_mocks do
|
||||
{:ok, %Tesla.Env{status: 404, body: ""}}
|
||||
end
|
||||
|
||||
def head(url, query, body, headers) do
|
||||
{:error,
|
||||
"Mock response not implemented for HEAD #{inspect(url)}, #{query}, #{inspect(body)}, #{
|
||||
inspect(headers)
|
||||
}"}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -440,6 +440,45 @@ test "it sends a welcome chat message if it is set" do
|
|||
assert activity.actor == welcome_user.ap_id
|
||||
end
|
||||
|
||||
setup do:
|
||||
clear_config(:mrf_simple,
|
||||
media_removal: [],
|
||||
media_nsfw: [],
|
||||
federated_timeline_removal: [],
|
||||
report_removal: [],
|
||||
reject: [],
|
||||
followers_only: [],
|
||||
accept: [],
|
||||
avatar_removal: [],
|
||||
banner_removal: [],
|
||||
reject_deletes: []
|
||||
)
|
||||
|
||||
setup do:
|
||||
clear_config(:mrf,
|
||||
policies: [
|
||||
Pleroma.Web.ActivityPub.MRF.SimplePolicy
|
||||
]
|
||||
)
|
||||
|
||||
test "it sends a welcome chat message when Simple policy applied to local instance" do
|
||||
Pleroma.Config.put([:mrf_simple, :media_nsfw], ["localhost"])
|
||||
|
||||
welcome_user = insert(:user)
|
||||
Pleroma.Config.put([:welcome, :chat_message, :enabled], true)
|
||||
Pleroma.Config.put([:welcome, :chat_message, :sender_nickname], welcome_user.nickname)
|
||||
Pleroma.Config.put([:welcome, :chat_message, :message], "Hello, this is a chat message")
|
||||
|
||||
cng = User.register_changeset(%User{}, @full_user_data)
|
||||
{:ok, registered_user} = User.register(cng)
|
||||
ObanHelpers.perform_all()
|
||||
|
||||
activity = Repo.one(Pleroma.Activity)
|
||||
assert registered_user.ap_id in activity.recipients
|
||||
assert Object.normalize(activity).data["content"] =~ "chat message"
|
||||
assert activity.actor == welcome_user.ap_id
|
||||
end
|
||||
|
||||
test "it sends a welcome email message if it is set" do
|
||||
welcome_user = insert(:user)
|
||||
Pleroma.Config.put([:welcome, :email, :enabled], true)
|
||||
|
@ -1658,7 +1697,7 @@ test "returns true when the account is itself" do
|
|||
assert User.visible_for(user, user) == :visible
|
||||
end
|
||||
|
||||
test "returns false when the account is unauthenticated and auth is required" do
|
||||
test "returns false when the account is unconfirmed and confirmation is required" do
|
||||
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||
|
||||
user = insert(:user, local: true, confirmation_pending: true)
|
||||
|
@ -1667,14 +1706,23 @@ test "returns false when the account is unauthenticated and auth is required" do
|
|||
refute User.visible_for(user, other_user) == :visible
|
||||
end
|
||||
|
||||
test "returns true when the account is unauthenticated and auth is not required" do
|
||||
test "returns true when the account is unconfirmed and confirmation is required but the account is remote" do
|
||||
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||
|
||||
user = insert(:user, local: false, confirmation_pending: true)
|
||||
other_user = insert(:user, local: true)
|
||||
|
||||
assert User.visible_for(user, other_user) == :visible
|
||||
end
|
||||
|
||||
test "returns true when the account is unconfirmed and confirmation is not required" do
|
||||
user = insert(:user, local: true, confirmation_pending: true)
|
||||
other_user = insert(:user, local: true)
|
||||
|
||||
assert User.visible_for(user, other_user) == :visible
|
||||
end
|
||||
|
||||
test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do
|
||||
test "returns true when the account is unconfirmed and being viewed by a privileged account (confirmation required)" do
|
||||
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||
|
||||
user = insert(:user, local: true, confirmation_pending: true)
|
||||
|
|
|
@ -1810,6 +1810,14 @@ test "public timeline with default reply_visibility `self`", %{users: %{u1: user
|
|||
|> Enum.map(& &1.id)
|
||||
|
||||
assert activities_ids == []
|
||||
|
||||
activities_ids =
|
||||
%{}
|
||||
|> Map.put(:reply_visibility, "self")
|
||||
|> Map.put(:reply_filtering_user, nil)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|
||||
assert activities_ids == []
|
||||
end
|
||||
|
||||
test "home timeline", %{users: %{u1: user}} do
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidatorTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
import Pleroma.Factory
|
||||
|
@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do
|
|||
end
|
||||
|
||||
test "a basic note validates", %{note: note} do
|
||||
%{valid?: true} = NoteValidator.cast_and_validate(note)
|
||||
%{valid?: true} = ArticleNoteValidator.cast_and_validate(note)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,75 @@
|
|||
# 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.ArticleHandlingTest do
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
test "Pterotype (Wordpress Plugin) Article" do
|
||||
Tesla.Mock.mock(fn %{url: "https://wedistribute.org/wp-json/pterotype/v1/actor/-blog"} ->
|
||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/wedistribute-user.json")}
|
||||
end)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/tesla_mock/wedistribute-create-article.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
object = Object.normalize(data["object"])
|
||||
|
||||
assert object.data["name"] == "The end is near: Mastodon plans to drop OStatus support"
|
||||
|
||||
assert object.data["summary"] ==
|
||||
"One of the largest platforms in the federated social web is dropping the protocol that it started with."
|
||||
|
||||
assert object.data["url"] == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/"
|
||||
end
|
||||
|
||||
test "Plume Article" do
|
||||
Tesla.Mock.mock(fn
|
||||
%{url: "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-article.json")
|
||||
}
|
||||
|
||||
%{url: "https://baptiste.gelez.xyz/@/BaptisteGelez"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/baptiste.gelex.xyz-user.json")
|
||||
}
|
||||
end)
|
||||
|
||||
{:ok, object} =
|
||||
Fetcher.fetch_object_from_id(
|
||||
"https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
|
||||
)
|
||||
|
||||
assert object.data["name"] == "This Month in Plume: June 2018"
|
||||
|
||||
assert object.data["url"] ==
|
||||
"https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
|
||||
end
|
||||
|
||||
test "Prismo Article" do
|
||||
Tesla.Mock.mock(fn %{url: "https://prismo.news/@mxb"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/tesla_mock/https___prismo.news__mxb.json")
|
||||
}
|
||||
end)
|
||||
|
||||
data = File.read!("test/fixtures/prismo-url-map.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
object = Object.normalize(data["object"])
|
||||
|
||||
assert object.data["url"] == "https://prismo.news/posts/83"
|
||||
end
|
||||
end
|
93
test/web/activity_pub/transmogrifier/video_handling_test.exs
Normal file
93
test/web/activity_pub/transmogrifier/video_handling_test.exs
Normal file
|
@ -0,0 +1,93 @@
|
|||
# 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.VideoHandlingTest do
|
||||
use Oban.Testing, repo: Pleroma.Repo
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
setup_all do
|
||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "skip converting the content when it is nil" do
|
||||
data =
|
||||
File.read!("test/fixtures/tesla_mock/framatube.org-video.json")
|
||||
|> Jason.decode!()
|
||||
|> Kernel.put_in(["object", "content"], nil)
|
||||
|
||||
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert object = Object.normalize(activity, false)
|
||||
|
||||
assert object.data["content"] == nil
|
||||
end
|
||||
|
||||
test "it converts content of object to html" do
|
||||
data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert object = Object.normalize(activity, false)
|
||||
|
||||
assert object.data["content"] ==
|
||||
"<p>Après avoir mené avec un certain succès la campagne « Dégooglisons Internet » en 2014, l’association Framasoft annonce fin 2019 arrêter progressivement un certain nombre de ses services alternatifs aux GAFAM. Pourquoi ?</p><p>Transcription par @aprilorg ici : <a href=\"https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft\">https://www.april.org/deframasoftisons-internet-pierre-yves-gosset-framasoft</a></p>"
|
||||
end
|
||||
|
||||
test "it remaps video URLs as attachments if necessary" do
|
||||
{:ok, object} =
|
||||
Fetcher.fetch_object_from_id(
|
||||
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
|
||||
)
|
||||
|
||||
assert object.data["url"] ==
|
||||
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
|
||||
|
||||
assert object.data["attachment"] == [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => "video/mp4",
|
||||
"name" => nil,
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
data = File.read!("test/fixtures/tesla_mock/framatube.org-video.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert object = Object.normalize(activity, false)
|
||||
|
||||
assert object.data["attachment"] == [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => "video/mp4",
|
||||
"name" => nil,
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
assert object.data["url"] ==
|
||||
"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
|
||||
end
|
||||
end
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
@ -45,15 +44,6 @@ test "it works for incoming notices with tag not being an array (kroeg)" do
|
|||
assert "test" in object.data["tag"]
|
||||
end
|
||||
|
||||
test "it works for incoming notices with url not being a string (prismo)" do
|
||||
data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
object = Object.normalize(data["object"])
|
||||
|
||||
assert object.data["url"] == "https://prismo.news/posts/83"
|
||||
end
|
||||
|
||||
test "it cleans up incoming notices which are not really DMs" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
@ -355,83 +345,6 @@ test "it works for incoming unfollows with an existing follow" do
|
|||
refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
|
||||
end
|
||||
|
||||
test "skip converting the content when it is nil" do
|
||||
object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
|
||||
|
||||
{:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id)
|
||||
|
||||
result =
|
||||
Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil}))
|
||||
|
||||
assert result["content"] == nil
|
||||
end
|
||||
|
||||
test "it converts content of object to html" do
|
||||
object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe"
|
||||
|
||||
{:ok, %{"content" => content_markdown}} =
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(object_id)
|
||||
|
||||
{:ok, %Pleroma.Object{data: %{"content" => content}} = object} =
|
||||
Fetcher.fetch_object_from_id(object_id)
|
||||
|
||||
assert content_markdown ==
|
||||
"Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..."
|
||||
|
||||
assert content ==
|
||||
"<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>"
|
||||
|
||||
assert object.data["mediaType"] == "text/html"
|
||||
end
|
||||
|
||||
test "it remaps video URLs as attachments if necessary" do
|
||||
{:ok, object} =
|
||||
Fetcher.fetch_object_from_id(
|
||||
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
|
||||
)
|
||||
|
||||
assert object.data["url"] ==
|
||||
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
|
||||
|
||||
assert object.data["attachment"] == [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => "video/mp4",
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
{:ok, object} =
|
||||
Fetcher.fetch_object_from_id(
|
||||
"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
|
||||
)
|
||||
|
||||
assert object.data["attachment"] == [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => "video/mp4",
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://framatube.org/static/webseed/6050732a-8a7a-43d4-a6cd-809525a1d206-1080.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
assert object.data["url"] ==
|
||||
"https://framatube.org/videos/watch/6050732a-8a7a-43d4-a6cd-809525a1d206"
|
||||
end
|
||||
|
||||
test "it accepts Flag activities" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
@ -1133,75 +1046,7 @@ test "fixes data for object when url is map" do
|
|||
}
|
||||
end
|
||||
|
||||
test "fixes data for video object" do
|
||||
object = %{
|
||||
"type" => "Video",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mimeType" => "video/mp4",
|
||||
"href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
|
||||
},
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mimeType" => "video/mp4",
|
||||
"href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
|
||||
},
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mimeType" => "text/html",
|
||||
"href" => "https://peertube.-2d4c2d1630e3"
|
||||
},
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mimeType" => "text/html",
|
||||
"href" => "https://peertube.-2d4c2d16377-42"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
assert Transmogrifier.fix_url(object) == %{
|
||||
"attachment" => [
|
||||
%{
|
||||
"href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
||||
"mimeType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
],
|
||||
"type" => "Video",
|
||||
"url" => "https://peertube.-2d4c2d1630e3"
|
||||
}
|
||||
end
|
||||
|
||||
test "fixes url for not Video object" do
|
||||
object = %{
|
||||
"type" => "Text",
|
||||
"url" => [
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mimeType" => "text/html",
|
||||
"href" => "https://peertube.-2d4c2d1630e3"
|
||||
},
|
||||
%{
|
||||
"type" => "Link",
|
||||
"mimeType" => "text/html",
|
||||
"href" => "https://peertube.-2d4c2d16377-42"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
assert Transmogrifier.fix_url(object) == %{
|
||||
"type" => "Text",
|
||||
"url" => "https://peertube.-2d4c2d1630e3"
|
||||
}
|
||||
|
||||
assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
|
||||
"type" => "Text",
|
||||
"url" => ""
|
||||
}
|
||||
end
|
||||
|
||||
test "retunrs not modified object" do
|
||||
test "returns non-modified object" do
|
||||
assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1442,7 +1442,10 @@ test "returns lists to which the account belongs" do
|
|||
describe "verify_credentials" do
|
||||
test "verify_credentials" do
|
||||
%{user: user, conn: conn} = oauth_access(["read:accounts"])
|
||||
[notification | _] = insert_list(7, :notification, user: user)
|
||||
|
||||
[notification | _] =
|
||||
insert_list(7, :notification, user: user, activity: insert(:note_activity))
|
||||
|
||||
Pleroma.Notification.set_read_up_to(user, notification.id)
|
||||
conn = get(conn, "/api/v1/accounts/verify_credentials")
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do
|
|||
test "gets markers with correct scopes", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
|
||||
insert_list(7, :notification, user: user)
|
||||
insert_list(7, :notification, user: user, activity: insert(:note_activity))
|
||||
|
||||
{:ok, %{"notifications" => marker}} =
|
||||
Pleroma.Marker.upsert(
|
||||
|
|
|
@ -448,7 +448,7 @@ test "shows unread_conversation_count only to the account owner" do
|
|||
|
||||
test "shows unread_count only to the account owner" do
|
||||
user = insert(:user)
|
||||
insert_list(7, :notification, user: user)
|
||||
insert_list(7, :notification, user: user, activity: insert(:note_activity))
|
||||
other_user = insert(:user)
|
||||
|
||||
user = User.get_cached_by_ap_id(user.ap_id)
|
||||
|
|
|
@ -56,6 +56,27 @@ defmodule Pleroma.Web.RichMedia.ParserTest do
|
|||
|
||||
%{method: :get, url: "http://example.com/error"} ->
|
||||
{:error, :overload}
|
||||
|
||||
%{
|
||||
method: :head,
|
||||
url: "http://example.com/huge-page"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
headers: [{"content-length", "2000001"}, {"content-type", "text/html"}]
|
||||
}
|
||||
|
||||
%{
|
||||
method: :head,
|
||||
url: "http://example.com/pdf-file"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}]
|
||||
}
|
||||
|
||||
%{method: :head} ->
|
||||
%Tesla.Env{status: 404, body: "", headers: []}
|
||||
end)
|
||||
|
||||
:ok
|
||||
|
@ -144,4 +165,12 @@ test "rejects invalid OGP data" do
|
|||
test "returns error if getting page was not successful" do
|
||||
assert {:error, :overload} = Parser.parse("http://example.com/error")
|
||||
end
|
||||
|
||||
test "does a HEAD request to check if the body is too large" do
|
||||
assert {:error, :body_too_large} = Parser.parse("http://example.com/huge-page")
|
||||
end
|
||||
|
||||
test "does a HEAD request to check if the body is html" do
|
||||
assert {:error, {:content_type, _}} = Parser.parse("http://example.com/pdf-file")
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue