summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/benchmark.ex44
-rw-r--r--lib/mix/tasks/pleroma/database.ex36
-rw-r--r--lib/mix/tasks/pleroma/relay.ex12
-rw-r--r--lib/pleroma/activity.ex28
-rw-r--r--lib/pleroma/application.ex191
-rw-r--r--lib/pleroma/captcha/captcha.ex2
-rw-r--r--lib/pleroma/config/transfer_task.ex2
-rw-r--r--lib/pleroma/conversation.ex11
-rw-r--r--lib/pleroma/conversation/participation.ex54
-rw-r--r--lib/pleroma/conversation/participation_recipient_ship.ex34
-rw-r--r--lib/pleroma/digest_email_worker.ex4
-rw-r--r--lib/pleroma/emoji.ex2
-rw-r--r--lib/pleroma/flake_id.ex2
-rw-r--r--lib/pleroma/gopher/server.ex2
-rw-r--r--lib/pleroma/html.ex30
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex12
-rw-r--r--lib/pleroma/scheduled_activity_worker.ex2
-rw-r--r--lib/pleroma/stats.ex60
-rw-r--r--lib/pleroma/uploaders/local.ex4
-rw-r--r--lib/pleroma/uploaders/mdii.ex2
-rw-r--r--lib/pleroma/uploaders/s3.ex12
-rw-r--r--lib/pleroma/user.ex81
-rw-r--r--lib/pleroma/user/info.ex51
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex40
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex34
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex6
-rw-r--r--lib/pleroma/web/activity_pub/mrf/drop_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/keyword_policy.ex32
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/mention_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/noop_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/normalize_markup.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/reject_non_public.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/mrf/subchain_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/tag_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex9
-rw-r--r--lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex37
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex3
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex17
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex60
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex9
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex12
-rw-r--r--lib/pleroma/web/chat_channel.ex4
-rw-r--r--lib/pleroma/web/common_api/common_api.ex22
-rw-r--r--lib/pleroma/web/common_api/utils.ex35
-rw-r--r--lib/pleroma/web/controller_helper.ex76
-rw-r--r--lib/pleroma/web/federator/retry_queue.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api.ex24
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex144
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex32
-rw-r--r--lib/pleroma/web/mastodon_api/views/conversation_view.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex44
-rw-r--r--lib/pleroma/web/nodeinfo/nodeinfo_controller.ex54
-rw-r--r--lib/pleroma/web/oauth/token/clean_worker.ex26
-rw-r--r--lib/pleroma/web/pleroma_api/pleroma_api_controller.ex73
-rw-r--r--lib/pleroma/web/router.ex15
-rw-r--r--lib/pleroma/web/streamer.ex27
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex13
-rw-r--r--lib/pleroma/web/web.ex22
64 files changed, 1112 insertions, 492 deletions
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index 5222cce80..4cc634727 100644
--- a/lib/mix/tasks/pleroma/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -26,4 +26,48 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
end
})
end
+
+ def run(["render_timeline", nickname]) do
+ start_pleroma()
+ user = Pleroma.User.get_by_nickname(nickname)
+
+ activities =
+ %{}
+ |> Map.put("type", ["Create", "Announce"])
+ |> Map.put("blocking_user", user)
+ |> Map.put("muting_user", user)
+ |> Map.put("user", user)
+ |> Map.put("limit", 80)
+ |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
+ |> Enum.reverse()
+
+ inputs = %{
+ "One activity" => Enum.take_random(activities, 1),
+ "Ten activities" => Enum.take_random(activities, 10),
+ "Twenty activities" => Enum.take_random(activities, 20),
+ "Forty activities" => Enum.take_random(activities, 40),
+ "Eighty activities" => Enum.take_random(activities, 80)
+ }
+
+ Benchee.run(
+ %{
+ "Parallel rendering" => fn activities ->
+ Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
+ activities: activities,
+ for: user,
+ as: :activity
+ })
+ end,
+ "Standart rendering" => fn activities ->
+ Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
+ activities: activities,
+ for: user,
+ as: :activity,
+ parallel: false
+ })
+ end
+ },
+ inputs: inputs
+ )
+ end
end
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 8547a329a..bcc2052d6 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -36,6 +36,10 @@ defmodule Mix.Tasks.Pleroma.Database do
## Remove duplicated items from following and update followers count for all users
mix pleroma.database update_users_following_followers_counts
+
+ ## Fix the pre-existing "likes" collections for all objects
+
+ mix pleroma.database fix_likes_collections
"""
def run(["remove_embedded_objects" | args]) do
{options, [], []} =
@@ -125,4 +129,36 @@ defmodule Mix.Tasks.Pleroma.Database do
)
end
end
+
+ def run(["fix_likes_collections"]) do
+ import Ecto.Query
+
+ start_pleroma()
+
+ from(object in Object,
+ where: fragment("(?)->>'likes' is not null", object.data),
+ select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
+ )
+ |> Pleroma.RepoStreamer.chunk_stream(100)
+ |> Stream.each(fn objects ->
+ ids =
+ objects
+ |> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end)
+ |> Enum.map(& &1.id)
+
+ Object
+ |> where([object], object.id in ^ids)
+ |> update([object],
+ set: [
+ data:
+ fragment(
+ "jsonb_set(?, '{likes}', '[]'::jsonb, true)",
+ object.data
+ )
+ ]
+ )
+ |> Repo.update_all([], timeout: :infinity)
+ end)
+ |> Stream.run()
+ end
end
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index c7324fff6..a738fae75 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -53,13 +53,11 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["list"]) do
start_pleroma()
- with %User{} = user <- Relay.get_actor() do
- user.following
- |> Enum.each(fn entry ->
- URI.parse(entry)
- |> Map.get(:host)
- |> shell_info()
- end)
+ with %User{following: following} = _user <- Relay.get_actor() do
+ following
+ |> Enum.map(fn entry -> URI.parse(entry).host end)
+ |> Enum.uniq()
+ |> Enum.each(&shell_info(&1))
else
e -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
end
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 46552c7be..35612c882 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -96,6 +96,7 @@ defmodule Pleroma.Activity do
from([a] in query,
left_join: tm in ThreadMute,
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
+ as: :thread_mute,
select: %Activity{a | thread_muted?: not is_nil(tm.id)}
)
end
@@ -224,6 +225,29 @@ defmodule Pleroma.Activity do
def get_create_by_object_ap_id(_), do: nil
+ def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do
+ from(
+ activity in Activity,
+ where:
+ fragment(
+ "coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
+ activity.data,
+ activity.data,
+ ^ap_ids
+ ),
+ where: fragment("(?)->>'type' = 'Create'", activity.data),
+ inner_join: o in Object,
+ on:
+ fragment(
+ "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
+ o.data,
+ activity.data,
+ activity.data
+ ),
+ preload: [object: o]
+ )
+ end
+
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
from(
activity in Activity,
@@ -263,8 +287,8 @@ defmodule Pleroma.Activity do
defp get_in_reply_to_activity_from_object(_), do: nil
- def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do
- get_in_reply_to_activity_from_object(Object.normalize(object))
+ def get_in_reply_to_activity(%Activity{} = activity) do
+ get_in_reply_to_activity_from_object(Object.normalize(activity))
end
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 00b06f723..25e56b9e2 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -3,11 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Application do
+ import Cachex.Spec
use Application
@name Mix.Project.config()[:name]
@version Mix.Project.config()[:version]
@repository Mix.Project.config()[:source_url]
+ @env Mix.env()
+
def name, do: @name
def version, do: @version
def named_version, do: @name <> " " <> @version
@@ -21,116 +24,24 @@ defmodule Pleroma.Application do
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
- import Cachex.Spec
-
Pleroma.Config.DeprecationWarnings.warn()
setup_instrumenters()
# Define workers and child supervisors to be supervised
children =
[
- # Start the Ecto repository
- %{id: Pleroma.Repo, start: {Pleroma.Repo, :start_link, []}, type: :supervisor},
- %{id: Pleroma.Config.TransferTask, start: {Pleroma.Config.TransferTask, :start_link, []}},
- %{id: Pleroma.Emoji, start: {Pleroma.Emoji, :start_link, []}},
- %{id: Pleroma.Captcha, start: {Pleroma.Captcha, :start_link, []}},
- %{
- id: :cachex_used_captcha_cache,
- start:
- {Cachex, :start_link,
- [
- :used_captcha_cache,
- [
- ttl_interval:
- :timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]))
- ]
- ]}
- },
- %{
- id: :cachex_user,
- start:
- {Cachex, :start_link,
- [
- :user_cache,
- [
- default_ttl: 25_000,
- ttl_interval: 1000,
- limit: 2500
- ]
- ]}
- },
- %{
- id: :cachex_object,
- start:
- {Cachex, :start_link,
- [
- :object_cache,
- [
- default_ttl: 25_000,
- ttl_interval: 1000,
- limit: 2500
- ]
- ]}
- },
- %{
- id: :cachex_rich_media,
- start:
- {Cachex, :start_link,
- [
- :rich_media_cache,
- [
- default_ttl: :timer.minutes(120),
- limit: 5000
- ]
- ]}
- },
- %{
- id: :cachex_scrubber,
- start:
- {Cachex, :start_link,
- [
- :scrubber_cache,
- [
- limit: 2500
- ]
- ]}
- },
- %{
- id: :cachex_idem,
- start:
- {Cachex, :start_link,
- [
- :idempotency_cache,
- [
- expiration:
- expiration(
- default: :timer.seconds(6 * 60 * 60),
- interval: :timer.seconds(60)
- ),
- limit: 2500
- ]
- ]}
- },
- %{id: Pleroma.FlakeId, start: {Pleroma.FlakeId, :start_link, []}},
- %{
- id: Pleroma.ScheduledActivityWorker,
- start: {Pleroma.ScheduledActivityWorker, :start_link, []}
- }
+ Pleroma.Repo,
+ Pleroma.Config.TransferTask,
+ Pleroma.Emoji,
+ Pleroma.Captcha,
+ Pleroma.FlakeId,
+ Pleroma.ScheduledActivityWorker
] ++
+ cachex_children() ++
hackney_pool_children() ++
[
- %{
- id: Pleroma.Web.Federator.RetryQueue,
- start: {Pleroma.Web.Federator.RetryQueue, :start_link, []}
- },
- %{
- id: Pleroma.Web.OAuth.Token.CleanWorker,
- start: {Pleroma.Web.OAuth.Token.CleanWorker, :start_link, []}
- },
- %{
- id: Pleroma.Stats,
- start: {Pleroma.Stats, :start_link, []}
- },
+ Pleroma.Web.Federator.RetryQueue,
+ Pleroma.Stats,
%{
id: :web_push_init,
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
@@ -147,16 +58,12 @@ defmodule Pleroma.Application do
restart: :temporary
}
] ++
- streamer_child() ++
- chat_child() ++
+ oauth_cleanup_child(oauth_cleanup_enabled?()) ++
+ streamer_child(@env) ++
+ chat_child(@env, chat_enabled?()) ++
[
- # Start the endpoint when the application starts
- %{
- id: Pleroma.Web.Endpoint,
- start: {Pleroma.Web.Endpoint, :start_link, []},
- type: :supervisor
- },
- %{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}
+ Pleroma.Web.Endpoint,
+ Pleroma.Gopher.Server
]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
@@ -201,28 +108,54 @@ defmodule Pleroma.Application do
end
end
- if Pleroma.Config.get(:env) == :test do
- defp streamer_child, do: []
- defp chat_child, do: []
- else
- defp streamer_child do
- [%{id: Pleroma.Web.Streamer, start: {Pleroma.Web.Streamer, :start_link, []}}]
- end
+ defp cachex_children do
+ [
+ build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
+ build_cachex("user", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
+ build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
+ build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
+ build_cachex("scrubber", limit: 2500),
+ build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500)
+ ]
+ end
- defp chat_child do
- if Pleroma.Config.get([:chat, :enabled]) do
- [
- %{
- id: Pleroma.Web.ChatChannel.ChatChannelState,
- start: {Pleroma.Web.ChatChannel.ChatChannelState, :start_link, []}
- }
- ]
- else
- []
- end
- end
+ defp idempotency_expiration,
+ do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
+
+ defp seconds_valid_interval,
+ do: :timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]))
+
+ defp build_cachex(type, opts),
+ do: %{
+ id: String.to_atom("cachex_" <> type),
+ start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
+ type: :worker
+ }
+
+ defp chat_enabled?, do: Pleroma.Config.get([:chat, :enabled])
+
+ defp oauth_cleanup_enabled?,
+ do: Pleroma.Config.get([:oauth2, :clean_expired_tokens], false)
+
+ defp streamer_child(:test), do: []
+
+ defp streamer_child(_) do
+ [Pleroma.Web.Streamer]
+ end
+
+ defp oauth_cleanup_child(true),
+ do: [Pleroma.Web.OAuth.Token.CleanWorker]
+
+ defp oauth_cleanup_child(_), do: []
+
+ defp chat_child(:test, _), do: []
+
+ defp chat_child(_env, true) do
+ [Pleroma.Web.ChatChannel.ChatChannelState]
end
+ defp chat_child(_, _), do: []
+
defp hackney_pool_children do
for pool <- enabled_hackney_pools() do
options = Pleroma.Config.get([:hackney_pools, pool])
diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex
index a73b87251..c2765a5b8 100644
--- a/lib/pleroma/captcha/captcha.ex
+++ b/lib/pleroma/captcha/captcha.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Captcha do
use GenServer
@doc false
- def start_link do
+ def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index 7799b2a78..3214c9951 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Config.TransferTask do
use Task
alias Pleroma.Web.AdminAPI.Config
- def start_link do
+ def start_link(_) do
load_and_update_env()
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo)
:ignore
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index bc97b39ca..be5821ad7 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Conversation do
alias Pleroma.Conversation.Participation
+ alias Pleroma.Conversation.Participation.RecipientShip
alias Pleroma.Repo
alias Pleroma.User
use Ecto.Schema
@@ -39,6 +40,15 @@ defmodule Pleroma.Conversation do
Repo.get_by(__MODULE__, ap_id: ap_id)
end
+ def maybe_create_recipientships(participation, activity) do
+ participation = Repo.preload(participation, :recipients)
+
+ if participation.recipients |> Enum.empty?() do
+ recipients = User.get_all_by_ap_id(activity.recipients)
+ RecipientShip.create(recipients, participation)
+ end
+ end
+
@doc """
This will
1. Create a conversation if there isn't one already
@@ -60,6 +70,7 @@ defmodule Pleroma.Conversation do
{:ok, participation} =
Participation.create_for_user_and_conversation(user, conversation, opts)
+ maybe_create_recipientships(participation, activity)
participation
end)
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index 5883e4183..ea5b9fe17 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Conversation.Participation do
use Ecto.Schema
alias Pleroma.Conversation
+ alias Pleroma.Conversation.Participation.RecipientShip
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -17,6 +18,9 @@ defmodule Pleroma.Conversation.Participation do
field(:read, :boolean, default: false)
field(:last_activity_id, Pleroma.FlakeId, virtual: true)
+ has_many(:recipient_ships, RecipientShip)
+ has_many(:recipients, through: [:recipient_ships, :user])
+
timestamps()
end
@@ -65,6 +69,14 @@ defmodule Pleroma.Conversation.Participation do
|> Pleroma.Pagination.fetch_paginated(params)
end
+ def for_user_and_conversation(user, conversation) do
+ from(p in __MODULE__,
+ where: p.user_id == ^user.id,
+ where: p.conversation_id == ^conversation.id
+ )
+ |> Repo.one()
+ end
+
def for_user_with_last_activity_id(user, params \\ %{}) do
for_user(user, params)
|> Enum.map(fn participation ->
@@ -81,4 +93,46 @@ defmodule Pleroma.Conversation.Participation do
end)
|> Enum.filter(& &1.last_activity_id)
end
+
+ def get(_, _ \\ [])
+ def get(nil, _), do: nil
+
+ def get(id, params) do
+ query =
+ if preload = params[:preload] do
+ from(p in __MODULE__,
+ preload: ^preload
+ )
+ else
+ __MODULE__
+ end
+
+ Repo.get(query, id)
+ end
+
+ def set_recipients(participation, user_ids) do
+ user_ids =
+ [participation.user_id | user_ids]
+ |> Enum.uniq()
+
+ Repo.transaction(fn ->
+ query =
+ from(r in RecipientShip,
+ where: r.participation_id == ^participation.id
+ )
+
+ Repo.delete_all(query)
+
+ users =
+ from(u in User,
+ where: u.id in ^user_ids
+ )
+ |> Repo.all()
+
+ RecipientShip.create(users, participation)
+ :ok
+ end)
+
+ {:ok, Repo.preload(participation, :recipients, force: true)}
+ end
end
diff --git a/lib/pleroma/conversation/participation_recipient_ship.ex b/lib/pleroma/conversation/participation_recipient_ship.ex
new file mode 100644
index 000000000..932cbd04c
--- /dev/null
+++ b/lib/pleroma/conversation/participation_recipient_ship.ex
@@ -0,0 +1,34 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Conversation.Participation.RecipientShip do
+ use Ecto.Schema
+
+ alias Pleroma.Conversation.Participation
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ import Ecto.Changeset
+
+ schema "conversation_participation_recipient_ships" do
+ belongs_to(:user, User, type: Pleroma.FlakeId)
+ belongs_to(:participation, Participation)
+ end
+
+ def creation_cng(struct, params) do
+ struct
+ |> cast(params, [:user_id, :participation_id])
+ |> validate_required([:user_id, :participation_id])
+ end
+
+ def create(%User{} = user, participation), do: create([user], participation)
+
+ def create(users, participation) do
+ Enum.each(users, fn user ->
+ %__MODULE__{}
+ |> creation_cng(%{user_id: user.id, participation_id: participation.id})
+ |> Repo.insert!()
+ end)
+ end
+end
diff --git a/lib/pleroma/digest_email_worker.ex b/lib/pleroma/digest_email_worker.ex
index 18e67d39b..5644d6a67 100644
--- a/lib/pleroma/digest_email_worker.ex
+++ b/lib/pleroma/digest_email_worker.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.DigestEmailWorker do
import Ecto.Query
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index 052501642..66e20f0e4 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.Emoji do
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
@doc false
- def start_link do
+ def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
diff --git a/lib/pleroma/flake_id.ex b/lib/pleroma/flake_id.ex
index ca0610abc..47d61ca5f 100644
--- a/lib/pleroma/flake_id.ex
+++ b/lib/pleroma/flake_id.ex
@@ -98,7 +98,7 @@ defmodule Pleroma.FlakeId do
def autogenerate, do: get()
# -- GenServer API
- def start_link do
+ def start_link(_) do
:gen_server.start_link({:local, :flake}, __MODULE__, [], [])
end
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index b3319e137..d4e4f3e55 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Gopher.Server do
use GenServer
require Logger
- def start_link do
+ def start_link(_) do
config = Pleroma.Config.get(:gopher, [])
ip = Keyword.get(config, :ip, {0, 0, 0, 0})
port = Keyword.get(config, :port, 1234)
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 2fae7281c..3951f0f51 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -203,6 +203,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("p", [])
Meta.allow_tag_with_these_attributes("pre", [])
Meta.allow_tag_with_these_attributes("strong", [])
+ Meta.allow_tag_with_these_attributes("sub", [])
+ Meta.allow_tag_with_these_attributes("sup", [])
Meta.allow_tag_with_these_attributes("u", [])
Meta.allow_tag_with_these_attributes("ul", [])
@@ -280,3 +282,31 @@ defmodule Pleroma.HTML.Transform.MediaProxy do
def scrub({_tag, children}), do: children
def scrub(text), do: text
end
+
+defmodule Pleroma.HTML.Scrubber.LinksOnly do
+ @moduledoc """
+ An HTML scrubbing policy which limits to links only.
+ """
+
+ @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
+
+ require HtmlSanitizeEx.Scrubber.Meta
+ alias HtmlSanitizeEx.Scrubber.Meta
+
+ Meta.remove_cdata_sections_before_scrub()
+ Meta.strip_comments()
+
+ # links
+ Meta.allow_tag_with_uri_attributes("a", ["href"], @valid_schemes)
+
+ Meta.allow_tag_with_this_attribute_values("a", "rel", [
+ "tag",
+ "nofollow",
+ "noopener",
+ "noreferrer",
+ "me"
+ ])
+
+ Meta.allow_tag_with_these_attributes("a", ["name", "title"])
+ Meta.strip_everything_not_covered()
+end
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 1f98f215c..03efad30a 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -109,7 +109,11 @@ defmodule Pleroma.ReverseProxy do
end
with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
- :ok <- header_length_constraint(headers, Keyword.get(opts, :max_body_length)) do
+ :ok <-
+ header_length_constraint(
+ headers,
+ Keyword.get(opts, :max_body_length, @max_body_length)
+ ) do
response(conn, client, url, code, headers, opts)
else
{:ok, code, headers} ->
@@ -200,7 +204,11 @@ defmodule Pleroma.ReverseProxy do
{:ok, data} <- client().stream_body(client),
{:ok, duration} <- increase_read_duration(duration),
sent_so_far = sent_so_far + byte_size(data),
- :ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
+ :ok <-
+ body_size_constraint(
+ sent_so_far,
+ Keyword.get(opts, :max_body_length, @max_body_length)
+ ),
{:ok, conn} <- chunk(conn, data) do
chunk_reply(conn, client, opts, sent_so_far, duration)
else
diff --git a/lib/pleroma/scheduled_activity_worker.ex b/lib/pleroma/scheduled_activity_worker.ex
index 65b38622f..8578cab5e 100644
--- a/lib/pleroma/scheduled_activity_worker.ex
+++ b/lib/pleroma/scheduled_activity_worker.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.ScheduledActivityWorker do
@schedule_interval :timer.minutes(1)
- def start_link do
+ def start_link(_) do
GenServer.start_link(__MODULE__, nil)
end
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 5b242927b..df80fbaa4 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -7,31 +7,56 @@ defmodule Pleroma.Stats do
alias Pleroma.Repo
alias Pleroma.User
- def start_link do
- agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)
- spawn(fn -> schedule_update() end)
- agent
+ use GenServer
+
+ @interval 1000 * 60 * 60
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, initial_data(), name: __MODULE__)
+ end
+
+ def force_update do
+ GenServer.call(__MODULE__, :force_update)
end
def get_stats do
- Agent.get(__MODULE__, fn {_, stats} -> stats end)
+ %{stats: stats} = GenServer.call(__MODULE__, :get_state)
+
+ stats
end
def get_peers do
- Agent.get(__MODULE__, fn {peers, _} -> peers end)
+ %{peers: peers} = GenServer.call(__MODULE__, :get_state)
+
+ peers
+ end
+
+ def init(args) do
+ Process.send(self(), :run_update, [])
+ {:ok, args}
+ end
+
+ def handle_call(:force_update, _from, _state) do
+ new_stats = get_stat_data()
+ {:reply, new_stats, new_stats}
end
- def schedule_update do
- spawn(fn ->
- # 1 hour
- Process.sleep(1000 * 60 * 60)
- schedule_update()
- end)
+ def handle_call(:get_state, _from, state) do
+ {:reply, state, state}
+ end
+
+ def handle_info(:run_update, _state) do
+ new_stats = get_stat_data()
+
+ Process.send_after(self(), :run_update, @interval)
+ {:noreply, new_stats}
+ end
- update_stats()
+ defp initial_data do
+ %{peers: [], stats: %{}}
end
- def update_stats do
+ defp get_stat_data do
peers =
from(
u in User,
@@ -52,8 +77,9 @@ defmodule Pleroma.Stats do
user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
- Agent.update(__MODULE__, fn _ ->
- {peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
- end)
+ %{
+ peers: peers,
+ stats: %{domain_count: domain_count, status_count: status_count, user_count: user_count}
+ }
end
end
diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex
index fc533da23..36b3c35ec 100644
--- a/lib/pleroma/uploaders/local.ex
+++ b/lib/pleroma/uploaders/local.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Uploaders.Local do
def put_file(upload) do
{local_path, file} =
- case Enum.reverse(String.split(upload.path, "/", trim: true)) do
+ case Enum.reverse(Path.split(upload.path)) do
[file] ->
{upload_path(), file}
@@ -23,7 +23,7 @@ defmodule Pleroma.Uploaders.Local do
result_file = Path.join(local_path, file)
- unless File.exists?(result_file) do
+ if not File.exists?(result_file) do
File.cp!(upload.tempfile, result_file)
end
diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex
index 237544337..c36f3d61d 100644
--- a/lib/pleroma/uploaders/mdii.ex
+++ b/lib/pleroma/uploaders/mdii.ex
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.MDII do
+ @moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
+
alias Pleroma.Config
alias Pleroma.HTTP
diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex
index 521daa93b..8c353bed3 100644
--- a/lib/pleroma/uploaders/s3.ex
+++ b/lib/pleroma/uploaders/s3.ex
@@ -6,10 +6,12 @@ defmodule Pleroma.Uploaders.S3 do
@behaviour Pleroma.Uploaders.Uploader
require Logger
+ alias Pleroma.Config
+
# The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames
def get_file(file) do
- config = Pleroma.Config.get([__MODULE__])
+ config = Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket)
bucket_with_namespace =
@@ -34,15 +36,15 @@ defmodule Pleroma.Uploaders.S3 do
end
def put_file(%Pleroma.Upload{} = upload) do
- config = Pleroma.Config.get([__MODULE__])
+ config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket)
- {:ok, file_data} = File.read(upload.tempfile)
-
s3_name = strict_encode(upload.path)
op =
- ExAws.S3.put_object(bucket, s3_name, file_data, [
+ upload.tempfile
+ |> ExAws.S3.Upload.stream_file()
+ |> ExAws.S3.upload(bucket, s3_name, [
{:acl, :public_read},
{:content_type, upload.content_type}
])
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 7d18f099e..134b8bb6c 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -21,6 +21,7 @@ defmodule Pleroma.User do
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.Web.OAuth
alias Pleroma.Web.OStatus
@@ -132,6 +133,28 @@ defmodule Pleroma.User do
|> Map.put(:follower_count, follower_count)
end
+ def follow_state(%User{} = user, %User{} = target) do
+ follow_activity = Utils.fetch_latest_follow(user, target)
+
+ if follow_activity,
+ do: follow_activity.data["state"],
+ # Ideally this would be nil, but then Cachex does not commit the value
+ else: false
+ end
+
+ def get_cached_follow_state(user, target) do
+ key = "follow_state:#{user.ap_id}|#{target.ap_id}"
+ Cachex.fetch!(:user_cache, key, fn _ -> {:commit, follow_state(user, target)} end)
+ end
+
+ def set_follow_state_cache(user_ap_id, target_ap_id, state) do
+ Cachex.put(
+ :user_cache,
+ "follow_state:#{user_ap_id}|#{target_ap_id}",
+ state
+ )
+ end
+
def set_info_cache(user, args) do
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
end
@@ -152,10 +175,10 @@ defmodule Pleroma.User do
end
def remote_user_creation(params) do
- params =
- params
- |> Map.put(:info, params[:info] || %{})
+ bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ params = Map.put(params, :info, params[:info] || %{})
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
changes =
@@ -164,8 +187,8 @@ defmodule Pleroma.User do
|> validate_required([:name, :ap_id])
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
- |> validate_length(:bio, max: 5000)
- |> validate_length(:name, max: 100)
+ |> validate_length(:bio, max: bio_limit)
+ |> validate_length(:name, max: name_limit)
|> put_change(:local, false)
|> put_embed(:info, info_cng)
@@ -188,22 +211,23 @@ defmodule Pleroma.User do
end
def update_changeset(struct, params \\ %{}) do
+ bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+
struct
|> cast(params, [:bio, :name, :avatar, :following])
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
- |> validate_length(:bio, max: 5000)
- |> validate_length(:name, min: 1, max: 100)
+ |> validate_length(:bio, max: bio_limit)
+ |> validate_length(:name, min: 1, max: name_limit)
end
- def upgrade_changeset(struct, params \\ %{}) do
- params =
- params
- |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
+ def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
+ bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
- info_cng =
- struct.info
- |> User.Info.user_upgrade(params[:info])
+ params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
+ info_cng = User.Info.user_upgrade(struct.info, params[:info], remote?)
struct
|> cast(params, [
@@ -216,8 +240,8 @@ defmodule Pleroma.User do
])
|> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex())
- |> validate_length(:bio, max: 5000)
- |> validate_length(:name, max: 100)
+ |> validate_length(:bio, max: bio_limit)
+ |> validate_length(:name, max: name_limit)
|> put_embed(:info, info_cng)
end
@@ -244,6 +268,9 @@ defmodule Pleroma.User do
end
def register_changeset(struct, params \\ %{}, opts \\ []) do
+ bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
Pleroma.Config.get([:instance, :account_activation_required])
@@ -264,8 +291,8 @@ defmodule Pleroma.User do
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex)
- |> validate_length(:bio, max: 1000)
- |> validate_length(:name, min: 1, max: 100)
+ |> validate_length(:bio, max: bio_limit)
+ |> validate_length(:name, min: 1, max: name_limit)
|> put_change(:info, info_change)
changeset =
@@ -459,6 +486,13 @@ defmodule Pleroma.User do
Repo.get_by(User, ap_id: ap_id)
end
+ def get_all_by_ap_id(ap_ids) do
+ from(u in __MODULE__,
+ where: u.ap_id in ^ap_ids
+ )
+ |> Repo.all()
+ end
+
# This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
# of the ap_id and the domain and tries to get that user
def get_by_guessed_nickname(ap_id) do
@@ -716,6 +750,7 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
+ @spec maybe_fetch_follow_information(User.t()) :: User.t()
def maybe_fetch_follow_information(user) do
with {:ok, user} <- fetch_follow_information(user) do
user
@@ -773,9 +808,10 @@ defmodule Pleroma.User do
end
end
+ @spec maybe_update_following_count(User.t()) :: User.t()
def maybe_update_following_count(%User{local: false} = user) do
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
- {:ok, maybe_fetch_follow_information(user)}
+ maybe_fetch_follow_information(user)
else
user
end
@@ -881,6 +917,13 @@ defmodule Pleroma.User do
blocker
end
+ # clear any requested follows as well
+ blocked =
+ case CommonAPI.reject_follow_request(blocked, blocker) do
+ {:ok, %User{} = updated_blocked} -> updated_blocked
+ nil -> blocked
+ end
+
blocker =
if subscribed_to?(blocked, blocker) do
{:ok, blocker} = unsubscribe(blocked, blocker)
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 22eb9a182..45a39924b 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -49,6 +49,8 @@ defmodule Pleroma.User.Info do
field(:mascot, :map, default: nil)
field(:emoji, {:array, :map}, default: [])
field(:pleroma_settings_store, :map, default: %{})
+ field(:fields, {:array, :map}, default: [])
+ field(:raw_fields, {:array, :map}, default: [])
field(:notification_settings, :map,
default: %{
@@ -254,11 +256,13 @@ defmodule Pleroma.User.Info do
:hide_followers,
:hide_follows,
:follower_count,
+ :fields,
:following_count
])
+ |> validate_fields(true)
end
- def user_upgrade(info, params) do
+ def user_upgrade(info, params, remote? \\ false) do
info
|> cast(params, [
:ap_enabled,
@@ -269,8 +273,10 @@ defmodule Pleroma.User.Info do
:follower_count,
:following_count,
:hide_follows,
+ :fields,
:hide_followers
])
+ |> validate_fields(remote?)
end
def profile_update(info, params) do
@@ -286,10 +292,40 @@ defmodule Pleroma.User.Info do
:background,
:show_role,
:skip_thread_containment,
+ :fields,
+ :raw_fields,
:pleroma_settings_store
])
+ |> validate_fields()
end
+ def validate_fields(changeset, remote? \\ false) do
+ limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
+ limit = Pleroma.Config.get([:instance, limit_name], 0)
+
+ changeset
+ |> validate_length(:fields, max: limit)
+ |> validate_change(:fields, fn :fields, fields ->
+ if Enum.all?(fields, &valid_field?/1) do
+ []
+ else
+ [fields: "invalid"]
+ end
+ end)
+ end
+
+ defp valid_field?(%{"name" => name, "value" => value}) do
+ name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
+ value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
+
+ is_binary(name) &&
+ is_binary(value) &&
+ String.length(name) <= name_limit &&
+ String.length(value) <= value_limit
+ end
+
+ defp valid_field?(_), do: false
+
@spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
def confirmation_changeset(info, opts) do
need_confirmation? = Keyword.get(opts, :need_confirmation)
@@ -384,6 +420,19 @@ defmodule Pleroma.User.Info do
cast(info, params, [:muted_reblogs])
end
+ # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
+ # For example: [{"name": "Pronoun", "value": "she/her"}, …]
+ def fields(%{fields: [], source_data: %{"attachment" => attachment}}) do
+ limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
+
+ attachment
+ |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
+ |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+ |> Enum.take(limit)
+ end
+
+ def fields(%{fields: fields}), do: fields
+
def follow_information_update(info, params) do
info
|> cast(params, [
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 1a279a7df..af9749d13 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -388,7 +388,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def follow(follower, followed, activity_id \\ nil, local \\ true) do
with data <- make_follow_data(follower, followed, activity_id),
{:ok, activity} <- insert(data, local),
- :ok <- maybe_federate(activity) do
+ :ok <- maybe_federate(activity),
+ _ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do
{:ok, activity}
end
end
@@ -518,6 +519,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from(activity in Activity)
|> maybe_preload_objects(opts)
+ |> maybe_preload_bookmarks(opts)
+ |> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts)
|> restrict_recipients(recipients, opts["user"])
|> where(
@@ -531,6 +534,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
)
)
|> exclude_poll_votes(opts)
+ |> exclude_id(opts)
|> order_by([activity], desc: activity.id)
end
@@ -623,6 +627,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
params =
params
|> Map.put("type", ["Create", "Announce"])
+ |> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true)
|> Map.put("pinned_activity_ids", user.info.pinned_activities)
@@ -786,14 +791,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
- defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do
+ defp restrict_muted(query, %{"muting_user" => %User{info: info}} = opts) do
mutes = info.mutes
- from(
- activity in query,
- where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
- where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
- )
+ query =
+ from([activity] in query,
+ where: fragment("not (? = ANY(?))", activity.actor, ^mutes),
+ where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes)
+ )
+
+ unless opts["skip_preload"] do
+ from([thread_mute: tm] in query, where: is_nil(tm))
+ else
+ query
+ end
end
defp restrict_muted(query, _), do: query
@@ -870,6 +881,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
+ defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
+ from(activity in query, where: activity.id != ^id)
+ end
+
+ defp exclude_id(query, _), do: query
+
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
defp maybe_preload_objects(query, _) do
@@ -888,7 +905,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp maybe_set_thread_muted_field(query, opts) do
query
- |> Activity.with_set_thread_muted_field(opts["user"])
+ |> Activity.with_set_thread_muted_field(opts["muting_user"] || opts["user"])
end
defp maybe_order(query, %{order: :desc}) do
@@ -1006,6 +1023,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"url" => [%{"href" => data["image"]["url"]}]
}
+ fields =
+ data
+ |> Map.get("attachment", [])
+ |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
+ |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+
locked = data["manuallyApprovesFollowers"] || false
data = Transmogrifier.maybe_fix_user_object(data)
@@ -1015,6 +1038,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
ap_enabled: true,
source_data: data,
banner: banner,
+ fields: fields,
locked: locked
},
avatar: avatar,
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index dd204b21c..263ed11af 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -28,11 +28,43 @@ defmodule Pleroma.Web.ActivityPub.MRF do
@spec subdomains_regex([String.t()]) :: [Regex.t()]
def subdomains_regex(domains) when is_list(domains) do
- for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)
+ for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
end
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
def subdomain_match?(domains, host) do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
+
+ @callback describe() :: {:ok | :error, Map.t()}
+
+ def describe(policies) do
+ {:ok, policy_configs} =
+ policies
+ |> Enum.reduce({:ok, %{}}, fn
+ policy, {:ok, data} ->
+ {:ok, policy_data} = policy.describe()
+ {:ok, Map.merge(data, policy_data)}
+
+ _, error ->
+ error
+ end)
+
+ mrf_policies =
+ get_policies()
+ |> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
+
+ exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+
+ base =
+ %{
+ mrf_policies: mrf_policies,
+ exclusions: length(exclusions) > 0
+ }
+ |> Map.merge(policy_configs)
+
+ {:ok, base}
+ end
+
+ def describe, do: get_policies() |> describe()
end
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index 87fa514c3..de1eb4aa5 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -62,4 +62,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
@impl true
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
index 2da3eac2f..b90193ca0 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -5,6 +5,8 @@
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
alias Pleroma.User
+ @behaviour Pleroma.Web.ActivityPub.MRF
+
require Logger
# has the user successfully posted before?
@@ -22,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
defp contains_links?(_), do: false
+ @impl true
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
{:contains_links, true} <- {:contains_links, contains_links?(object)},
@@ -45,4 +48,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
# in all other cases, pass through
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
index b8d38aae6..f7831bc3e 100644
--- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex
@@ -12,4 +12,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
Logger.info("REJECTING #{inspect(object)}")
{:reject, object}
end
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
index 2d03df68a..3a3e72910 100644
--- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -39,4 +39,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
end
def filter(object), do: {:ok, object}
+
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
index 377987cf2..b3c742954 100644
--- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex
@@ -90,4 +90,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@impl true
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe,
+ do: {:ok, %{mrf_hellthread: Pleroma.Config.get(:mrf_hellthread) |> Enum.into(%{})}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
index 4eec8b916..d6d1396bc 100644
--- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
@@ -96,4 +96,36 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
@impl true
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe do
+ # This horror is needed to convert regex sigils to strings
+ mrf_keyword =
+ Pleroma.Config.get(:mrf_keyword, [])
+ |> Enum.map(fn {key, value} ->
+ {key,
+ Enum.map(value, fn
+ {pattern, replacement} ->
+ %{
+ "pattern" =>
+ if not is_binary(pattern) do
+ inspect(pattern)
+ else
+ pattern
+ end,
+ "replacement" => replacement
+ }
+
+ pattern ->
+ if not is_binary(pattern) do
+ inspect(pattern)
+ else
+ pattern
+ end
+ end)}
+ end)
+ |> Enum.into(%{})
+
+ {:ok, %{mrf_keyword: mrf_keyword}}
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
index 01d21a299..a179dd54d 100644
--- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex
@@ -53,4 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
@impl true
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
index 1842e1aeb..ce8bc4580 100644
--- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex
@@ -21,4 +21,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
@impl true
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
index 86a48bda5..f67f48ab6 100644
--- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex
@@ -19,4 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
@impl true
def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex b/lib/pleroma/web/activity_pub/mrf/noop_policy.ex
index c47cb3298..878c57925 100644
--- a/lib/pleroma/web/activity_pub/mrf/noop_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/noop_policy.ex
@@ -10,4 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
def filter(object) do
{:ok, object}
end
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
index c269d0f89..daa4c88ad 100644
--- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
+++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex
@@ -21,4 +21,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
end
def filter(object), do: {:ok, object}
+
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
index 457b6ee10..5a809a321 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -44,4 +44,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true
def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe,
+ do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index f266457e3..8aa6852f0 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -177,4 +177,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end
def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe do
+ exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
+
+ mrf_simple =
+ Pleroma.Config.get(:mrf_simple)
+ |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
+ |> Enum.into(%{})
+
+ {:ok, %{mrf_simple: mrf_simple}}
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
index 765704389..566c1e191 100644
--- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
@@ -37,4 +37,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@impl true
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
index 70edf4f7f..c1801d2ec 100644
--- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex
@@ -165,4 +165,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
@impl true
def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
index e35d2c422..7389d6a96 100644
--- a/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/user_allowlist_policy.ex
@@ -32,4 +32,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
end
def filter(object), do: {:ok, object}
+
+ @impl true
+ def describe do
+ mrf_user_allowlist =
+ Config.get([:mrf_user_allowlist], [])
+ |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
+
+ {:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
new file mode 100644
index 000000000..4eaea00d8
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex
@@ -0,0 +1,37 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
+ @moduledoc "Filter messages which belong to certain activity vocabularies"
+
+ @behaviour Pleroma.Web.ActivityPub.MRF
+
+ def filter(%{"type" => "Undo", "object" => child_message} = message) do
+ with {:ok, _} <- filter(child_message) do
+ {:ok, message}
+ else
+ {:reject, nil} ->
+ {:reject, nil}
+ end
+ end
+
+ def filter(%{"type" => message_type} = message) do
+ with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
+ rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
+ true <-
+ length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
+ false <-
+ length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
+ {:ok, _} <- filter(message["object"]) do
+ {:ok, message}
+ else
+ _ -> {:reject, nil}
+ end
+ end
+
+ def filter(message), do: {:ok, message}
+
+ def describe,
+ do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
+end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 46edab0bd..262529b84 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"""
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.info("Federating #{id} to #{inbox}")
- host = URI.parse(inbox).host
+ %{host: host, path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@@ -56,6 +56,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
signature =
Pleroma.Signature.sign(actor, %{
+ "(request-target)": "post #{path}",
host: host,
"content-length": byte_size(json),
digest: digest,
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 1ebfcdd86..5f18cc64a 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|> User.get_or_create_service_actor_by_ap_id()
end
+ @spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def follow(target_instance) do
with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
@@ -21,12 +22,17 @@ defmodule Pleroma.Web.ActivityPub.Relay do
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity}
else
+ {:error, _} = error ->
+ Logger.error("error: #{inspect(error)}")
+ error
+
e ->
Logger.error("error: #{inspect(e)}")
{:error, e}
end
end
+ @spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def unfollow(target_instance) do
with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
@@ -34,20 +40,27 @@ defmodule Pleroma.Web.ActivityPub.Relay do
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
{:ok, activity}
else
+ {:error, _} = error ->
+ Logger.error("error: #{inspect(error)}")
+ error
+
e ->
Logger.error("error: #{inspect(e)}")
{:error, e}
end
end
+ @spec publish(any()) :: {:ok, Activity.t(), Object.t()} | {:error, any()}
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
with %User{} = user <- get_actor(),
%Object{} = object <- Object.normalize(activity) do
ActivityPub.announce(user, object, nil, true, false)
else
- e -> Logger.error("error: #{inspect(e)}")
+ e ->
+ Logger.error("error: #{inspect(e)}")
+ {:error, inspect(e)}
end
end
- def publish(_), do: nil
+ def publish(_), do: {:error, "Not implemented"}
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 5403b71d8..36340a3a1 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"""
def fix_object(object, options \\ []) do
object
+ |> strip_internal_fields
|> fix_actor
|> fix_url
|> fix_attachments
@@ -34,7 +35,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_emoji
|> fix_tag
|> fix_content_map
- |> fix_likes
|> fix_addressing
|> fix_summary
|> fix_type(options)
@@ -151,20 +151,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
end
- # Check for standardisation
- # This is what Peertube does
- # curl -H 'Accept: application/activity+json' $likes | jq .totalItems
- # Prismo returns only an integer (count) as "likes"
- def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
- object
- |> Map.put("likes", [])
- |> Map.put("like_count", 0)
- end
-
- def fix_likes(object) do
- object
- end
-
def fix_in_reply_to(object, options \\ [])
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
@@ -347,13 +333,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_type(object, options \\ [])
- def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do
+ def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
+ when is_binary(reply_id) do
reply =
- if Federator.allowed_incoming_reply_depth?(options[:depth]) do
- Object.normalize(reply_id, true)
+ with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
+ {:ok, object} <- get_obj_helper(reply_id, options) do
+ object
end
- if reply && (reply.data["type"] == "Question" and object["name"]) do
+ if reply && reply.data["type"] == "Question" do
Map.put(object, "type", "Answer")
else
object
@@ -610,14 +598,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
banner = new_user_data[:info][:banner]
locked = new_user_data[:info][:locked] || false
+ attachment = get_in(new_user_data, [:info, :source_data, "attachment"]) || []
+
+ fields =
+ attachment
+ |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
+ |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
update_data =
new_user_data
|> Map.take([:name, :bio, :avatar])
- |> Map.put(:info, %{banner: banner, locked: locked})
+ |> Map.put(:info, %{banner: banner, locked: locked, fields: fields})
actor
- |> User.upgrade_changeset(update_data)
+ |> User.upgrade_changeset(update_data, true)
|> User.update_and_set_cache()
ActivityPub.update(%{
@@ -713,8 +707,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
} = _data,
_options
) do
- with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
- %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
+ with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
User.unblock(blocker, blocked)
@@ -728,8 +721,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
_options
) do
- with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
- %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
+ with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
User.unfollow(blocker, blocked)
@@ -784,7 +776,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> add_mention_tags
|> add_emoji_tags
|> add_attributed_to
- |> add_likes
|> prepare_attachments
|> set_conversation
|> set_reply_to_uri
@@ -971,22 +962,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("attributedTo", attributed_to)
end
- def add_likes(%{"id" => id, "like_count" => likes} = object) do
- likes = %{
- "id" => "#{id}/likes",
- "first" => "#{id}/likes?page=1",
- "type" => "OrderedCollection",
- "totalItems" => likes
- }
-
- object
- |> Map.put("likes", likes)
- end
-
- def add_likes(object) do
- object
- end
-
def prepare_attachments(object) do
attachments =
(object["attachment"] || [])
@@ -1002,6 +977,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_fields(object) do
object
|> Map.drop([
+ "likes",
"like_count",
"announcements",
"announcement_count",
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index fc5305c58..1c3058658 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -374,6 +374,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
[state, actor, object]
)
+ User.set_follow_state_cache(actor, object, state)
activity = Activity.get_by_id(activity.id)
{:ok, activity}
rescue
@@ -382,12 +383,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
end
- def update_follow_state(%Activity{} = activity, state) do
+ def update_follow_state(
+ %Activity{data: %{"actor" => actor, "object" => object}} = activity,
+ state
+ ) do
with new_data <-
activity.data
|> Map.put("state", state),
changeset <- Changeset.change(activity, data: new_data),
- {:ok, activity} <- Repo.update(changeset) do
+ {:ok, activity} <- Repo.update(changeset),
+ _ <- User.set_follow_state_cache(actor, object, state) do
{:ok, activity}
end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 06c9e1c71..7be734b26 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -80,6 +80,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|> Transmogrifier.add_emoji_tags()
|> Map.get("tag", [])
+ fields =
+ user.info
+ |> User.Info.fields()
+ |> Enum.map(fn %{"name" => name, "value" => value} ->
+ %{
+ "name" => Pleroma.HTML.strip_tags(name),
+ "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
+ }
+ end)
+ |> Enum.map(&Map.put(&1, "type", "PropertyValue"))
+
%{
"id" => user.ap_id,
"type" => "Person",
@@ -98,6 +109,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"publicKeyPem" => public_key
},
"endpoints" => endpoints,
+ "attachment" => fields,
"tag" => (user.info.source_data["tag"] || []) ++ user_tags
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex
index f63f4bda1..b543909f1 100644
--- a/lib/pleroma/web/chat_channel.ex
+++ b/lib/pleroma/web/chat_channel.ex
@@ -33,9 +33,11 @@ defmodule Pleroma.Web.ChatChannel do
end
defmodule Pleroma.Web.ChatChannel.ChatChannelState do
+ use Agent
+
@max_messages 20
- def start_link do
+ def start_link(_) do
Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__)
end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 2db58324b..72da46263 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
+ alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.ThreadMute
@@ -171,21 +172,25 @@ defmodule Pleroma.Web.CommonAPI do
end)
end
- def get_visibility(%{"visibility" => visibility}, in_reply_to)
+ def get_visibility(_, _, %Participation{}) do
+ {"direct", "direct"}
+ end
+
+ def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
when visibility in ~w{public unlisted private direct},
do: {visibility, get_replied_to_visibility(in_reply_to)}
- def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to) do
+ def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to, _) do
visibility = {:list, String.to_integer(list_id)}
{visibility, get_replied_to_visibility(in_reply_to)}
end
- def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do
+ def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do
visibility = get_replied_to_visibility(in_reply_to)
{visibility, visibility}
end
- def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)}
+ def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)}
def get_replied_to_visibility(nil), do: nil
@@ -201,7 +206,9 @@ defmodule Pleroma.Web.CommonAPI do
with status <- String.trim(status),
attachments <- attachments_from_ids(data),
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
- {visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to),
+ in_reply_to_conversation <- Participation.get(data["in_reply_to_conversation_id"]),
+ {visibility, in_reply_to_visibility} <-
+ get_visibility(data, in_reply_to, in_reply_to_conversation),
{_, false} <-
{:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
{content_html, mentions, tags} <-
@@ -214,8 +221,9 @@ defmodule Pleroma.Web.CommonAPI do
mentioned_users <- for({_, mentioned_user} <- mentions, do: mentioned_user.ap_id),
addressed_users <- get_addressed_users(mentioned_users, data["to"]),
{poll, poll_emoji} <- make_poll_data(data),
- {to, cc} <- get_to_and_cc(user, addressed_users, in_reply_to, visibility),
- context <- make_context(in_reply_to),
+ {to, cc} <-
+ get_to_and_cc(user, addressed_users, in_reply_to, visibility, in_reply_to_conversation),
+ context <- make_context(in_reply_to, in_reply_to_conversation),
cw <- data["spoiler_text"] || "",
sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
full_payload <- String.trim(status <> cw),
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 22c44a0a3..61b96aba9 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Calendar.Strftime
alias Pleroma.Activity
alias Pleroma.Config
+ alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.Plugs.AuthenticationPlug
@@ -86,9 +87,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Enum.filter(& &1)
end
- @spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) ::
+ @spec get_to_and_cc(
+ User.t(),
+ list(String.t()),
+ Activity.t() | nil,
+ String.t(),
+ Participation.t() | nil
+ ) ::
{list(String.t()), list(String.t())}
- def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do
+
+ def get_to_and_cc(_, _, _, _, %Participation{} = participation) do
+ participation = Repo.preload(participation, :recipients)
+ {Enum.map(participation.recipients, & &1.ap_id), []}
+ end
+
+ def get_to_and_cc(user, mentioned_users, inReplyTo, "public", _) do
to = [Pleroma.Constants.as_public() | mentioned_users]
cc = [user.follower_address]
@@ -99,7 +112,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
- def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do
+ def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted", _) do
to = [user.follower_address | mentioned_users]
cc = [Pleroma.Constants.as_public()]
@@ -110,12 +123,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
- def get_to_and_cc(user, mentioned_users, inReplyTo, "private") do
- {to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct")
+ def get_to_and_cc(user, mentioned_users, inReplyTo, "private", _) do
+ {to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct", nil)
{[user.follower_address | to], cc}
end
- def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct") do
+ def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct", _) do
if inReplyTo do
{Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []}
else
@@ -123,7 +136,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
- def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}), do: {mentions, []}
+ def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}, _), do: {mentions, []}
def get_addressed_users(_, to) when is_list(to) do
User.get_ap_ids_by_nicknames(to)
@@ -253,8 +266,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
defp maybe_add_nsfw_tag(data, _), do: data
- def make_context(%Activity{data: %{"context" => context}}), do: context
- def make_context(_), do: Utils.generate_context_id()
+ def make_context(_, %Participation{} = participation) do
+ Repo.preload(participation, :conversation).conversation.ap_id
+ end
+
+ def make_context(%Activity{data: %{"context" => context}}, _), do: context
+ def make_context(_, _), do: Utils.generate_context_id()
def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 8a753bb4f..eeac9f503 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -33,4 +33,80 @@ defmodule Pleroma.Web.ControllerHelper do
end
defp param_to_integer(_, default), do: default
+
+ def add_link_headers(
+ conn,
+ method,
+ activities,
+ param \\ nil,
+ params \\ %{},
+ func3 \\ nil,
+ func4 \\ nil
+ ) do
+ params =
+ conn.params
+ |> Map.drop(["since_id", "max_id", "min_id"])
+ |> Map.merge(params)
+
+ last = List.last(activities)
+
+ func3 = func3 || (&mastodon_api_url/3)
+ func4 = func4 || (&mastodon_api_url/4)
+
+ if last do
+ max_id = last.id
+
+ limit =
+ params
+ |> Map.get("limit", "20")
+ |> String.to_integer()
+
+ min_id =
+ if length(activities) <= limit do
+ activities
+ |> List.first()
+ |> Map.get(:id)
+ else
+ activities
+ |> Enum.at(limit * -1)
+ |> Map.get(:id)
+ end
+
+ {next_url, prev_url} =
+ if param do
+ {
+ func4.(
+ Pleroma.Web.Endpoint,
+ method,
+ param,
+ Map.merge(params, %{max_id: max_id})
+ ),
+ func4.(
+ Pleroma.Web.Endpoint,
+ method,
+ param,
+ Map.merge(params, %{min_id: min_id})
+ )
+ }
+ else
+ {
+ func3.(
+ Pleroma.Web.Endpoint,
+ method,
+ Map.merge(params, %{max_id: max_id})
+ ),
+ func3.(
+ Pleroma.Web.Endpoint,
+ method,
+ Map.merge(params, %{min_id: min_id})
+ )
+ }
+ end
+
+ conn
+ |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
+ else
+ conn
+ end
+ end
end
diff --git a/lib/pleroma/web/federator/retry_queue.ex b/lib/pleroma/web/federator/retry_queue.ex
index 3db948c2e..9eab8c218 100644
--- a/lib/pleroma/web/federator/retry_queue.ex
+++ b/lib/pleroma/web/federator/retry_queue.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
{:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}}
end
- def start_link do
+ def start_link(_) do
enabled =
if Pleroma.Config.get(:env) == :test,
do: true,
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index 46944dcbc..ac01d1ff3 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -13,10 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
+ @spec follow(User.t(), User.t(), map) :: {:ok, User.t()} | {:error, String.t()}
def follow(follower, followed, params \\ %{}) do
- options = cast_params(params)
- reblogs = options[:reblogs]
-
result =
if not User.following?(follower, followed) do
CommonAPI.follow(follower, followed)
@@ -24,19 +22,25 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
{:ok, follower, followed, nil}
end
- with {:ok, follower, followed, _} <- result do
- reblogs
- |> case do
- false -> CommonAPI.hide_reblogs(follower, followed)
- _ -> CommonAPI.show_reblogs(follower, followed)
- end
- |> case do
+ with {:ok, follower, _followed, _} <- result do
+ options = cast_params(params)
+
+ case reblogs_visibility(options[:reblogs], result) do
{:ok, follower} -> {:ok, follower}
_ -> {:ok, follower}
end
end
end
+ defp reblogs_visibility(false, {:ok, follower, followed, _}) do
+ CommonAPI.hide_reblogs(follower, followed)
+ end
+
+ defp reblogs_visibility(_, {:ok, follower, followed, _}) do
+ CommonAPI.show_reblogs(follower, followed)
+ end
+
+ @spec get_followers(User.t(), map()) :: list(User.t())
def get_followers(user, params \\ %{}) do
user
|> User.get_followers_query()
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 174e93468..53cf95fbb 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -5,7 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
- import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+ import Pleroma.Web.ControllerHelper,
+ only: [json_response: 3, add_link_headers: 5, add_link_headers: 4, add_link_headers: 3]
alias Ecto.Changeset
alias Pleroma.Activity
@@ -137,7 +138,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
user_info_emojis =
- ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
+ user.info
+ |> Map.get(:emoji, [])
+ |> Enum.concat(Formatter.get_emoji_map(emojis_text))
|> Enum.dedup()
info_params =
@@ -156,6 +159,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end)
end)
|> add_if_present(params, "default_scope", :default_scope)
+ |> add_if_present(params, "fields", :fields, fn fields ->
+ fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
+
+ {:ok, fields}
+ end)
+ |> add_if_present(params, "fields", :raw_fields)
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
{:ok, Map.merge(user.info.pleroma_settings_store, value)}
end)
@@ -342,71 +351,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, mastodon_emoji)
end
- defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do
- params =
- conn.params
- |> Map.drop(["since_id", "max_id", "min_id"])
- |> Map.merge(params)
-
- last = List.last(activities)
-
- if last do
- max_id = last.id
-
- limit =
- params
- |> Map.get("limit", "20")
- |> String.to_integer()
-
- min_id =
- if length(activities) <= limit do
- activities
- |> List.first()
- |> Map.get(:id)
- else
- activities
- |> Enum.at(limit * -1)
- |> Map.get(:id)
- end
-
- {next_url, prev_url} =
- if param do
- {
- mastodon_api_url(
- Pleroma.Web.Endpoint,
- method,
- param,
- Map.merge(params, %{max_id: max_id})
- ),
- mastodon_api_url(
- Pleroma.Web.Endpoint,
- method,
- param,
- Map.merge(params, %{min_id: min_id})
- )
- }
- else
- {
- mastodon_api_url(
- Pleroma.Web.Endpoint,
- method,
- Map.merge(params, %{max_id: max_id})
- ),
- mastodon_api_url(
- Pleroma.Web.Endpoint,
- method,
- Map.merge(params, %{min_id: min_id})
- )
- }
- end
-
- conn
- |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
- else
- conn
- end
- end
-
def home_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
@@ -435,6 +379,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
+ |> Map.put("user", user)
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
@@ -496,12 +441,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
activities <-
ActivityPub.fetch_activities_for_context(activity.data["context"], %{
"blocking_user" => user,
- "user" => user
+ "user" => user,
+ "exclude_id" => activity.id
}),
- activities <-
- activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
- activities <-
- activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
result = %{
ancestors:
@@ -536,8 +478,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> put_view(StatusView)
|> try_render("poll.json", %{object: object, for: user})
else
- nil -> render_error(conn, :not_found, "Record not found")
- false -> render_error(conn, :not_found, "Record not found")
+ error when is_nil(error) or error == false ->
+ render_error(conn, :not_found, "Record not found")
end
end
@@ -885,8 +827,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
- %Object{data: %{"likes" => likes}} <- Object.normalize(object) do
+ with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^likes)
users =
@@ -902,8 +844,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
- %Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
+ with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^announces)
users =
@@ -944,6 +886,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
+ |> Map.put("user", user)
|> Map.put("tag", tags)
|> Map.put("tag_all", tag_all)
|> Map.put("tag_reject", tag_reject)
@@ -1350,6 +1293,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
params
|> Map.put("type", "Create")
|> Map.put("blocking_user", user)
+ |> Map.put("user", user)
|> Map.put("muting_user", user)
# we must filter the following list for the user to avoid leaking statuses the user
@@ -1690,45 +1634,35 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> String.replace("{{user}}", user)
with {:ok, %{status: 200, body: body}} <-
- HTTP.get(
- url,
- [],
- adapter: [
- recv_timeout: timeout,
- pool: :default
- ]
- ),
+ HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
{:ok, data} <- Jason.decode(body) do
data =
data
|> Enum.slice(0, limit)
|> Enum.map(fn x ->
- Map.put(
- x,
- "id",
- case User.get_or_fetch(x["acct"]) do
- {:ok, %User{id: id}} -> id
- _ -> 0
- end
- )
- end)
- |> Enum.map(fn x ->
- Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
- end)
- |> Enum.map(fn x ->
- Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
+ x
+ |> Map.put("id", fetch_suggestion_id(x))
+ |> Map.put("avatar", MediaProxy.url(x["avatar"]))
+ |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
end)
- conn
- |> json(data)
+ json(conn, data)
else
- e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
+ e ->
+ Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end
else
json(conn, [])
end
end
+ defp fetch_suggestion_id(attrs) do
+ case User.get_or_fetch(attrs["acct"]) do
+ {:ok, %User{id: id}} -> id
+ _ -> 0
+ end
+ end
+
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
with %Activity{} = activity <- Activity.get_by_id(status_id),
true <- Visibility.visible_for_user?(activity, user) do
@@ -1807,7 +1741,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conversations =
Enum.map(participations, fn participation ->
- ConversationView.render("participation.json", %{participation: participation, user: user})
+ ConversationView.render("participation.json", %{participation: participation, for: user})
end)
conn
@@ -1820,7 +1754,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
Repo.get_by(Participation, id: participation_id, user_id: user.id),
{:ok, participation} <- Participation.mark_as_read(participation) do
participation_view =
- ConversationView.render("participation.json", %{participation: participation, user: user})
+ ConversationView.render("participation.json", %{participation: participation, for: user})
conn
|> json(participation_view)
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index de084fd6e..169116d0d 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -37,11 +37,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do
- follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
+ follow_state = User.get_cached_follow_state(user, target)
requested =
- if follow_activity && !User.following?(target, user) do
- follow_activity.data["state"] == "pending"
+ if follow_state && !User.following?(user, target) do
+ follow_state == "pending"
else
false
end
@@ -72,6 +72,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.get_cached_user_info(user)
+
+ following_count =
+ ((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0
+
+ followers_count =
+ ((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0
+
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
emojis =
@@ -87,12 +94,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end)
fields =
- (user.info.source_data["attachment"] || [])
- |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
- |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+ user.info
+ |> User.Info.fields()
+ |> Enum.map(fn %{"name" => name, "value" => value} ->
+ %{
+ "name" => Pleroma.HTML.strip_tags(name),
+ "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
+ }
+ end)
- bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
+ raw_fields = Map.get(user.info, :raw_fields, [])
+ bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
relationship = render("relationship.json", %{user: opts[:for], target: user})
%{
@@ -102,8 +115,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
display_name: display_name,
locked: user_info.locked,
created_at: Utils.to_masto_date(user.inserted_at),
- followers_count: user_info.follower_count,
- following_count: user_info.following_count,
+ followers_count: followers_count,
+ following_count: following_count,
statuses_count: user_info.note_count,
note: bio || "",
url: User.profile_url(user),
@@ -117,6 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
source: %{
note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
sensitive: false,
+ fields: raw_fields,
pleroma: %{}
},
diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
index 38bdec737..40acc07b3 100644
--- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex
@@ -11,8 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
- def render("participation.json", %{participation: participation, user: user}) do
- participation = Repo.preload(participation, conversation: :users)
+ def render("participation.json", %{participation: participation, for: user}) do
+ participation = Repo.preload(participation, conversation: [], recipients: [])
last_activity_id =
with nil <- participation.last_activity_id do
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do
# Conversations return all users except the current user.
users =
- participation.conversation.users
+ participation.recipients
|> Enum.reject(&(&1.id == user.id))
accounts =
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 80df9b2ac..42fbdf51b 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -5,7 +5,11 @@
defmodule Pleroma.Web.MastodonAPI.StatusView do
use Pleroma.Web, :view
+ require Pleroma.Constants
+
alias Pleroma.Activity
+ alias Pleroma.Conversation
+ alias Pleroma.Conversation.Participation
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
@@ -24,19 +28,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
defp get_replied_to_activities(activities) do
activities
|> Enum.map(fn
- %{data: %{"type" => "Create", "object" => object}} ->
- object = Object.normalize(object)
- object.data["inReplyTo"] != "" && object.data["inReplyTo"]
+ %{data: %{"type" => "Create"}} = activity ->
+ object = Object.normalize(activity)
+ object && object.data["inReplyTo"] != "" && object.data["inReplyTo"]
_ ->
nil
end)
|> Enum.filter(& &1)
- |> Activity.create_by_object_ap_id()
+ |> Activity.create_by_object_ap_id_with_object()
|> Repo.all()
|> Enum.reduce(%{}, fn activity, acc ->
object = Object.normalize(activity)
- Map.put(acc, object.data["id"], activity)
+ if object, do: Map.put(acc, object.data["id"], activity), else: acc
end)
end
@@ -68,12 +72,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
def render("index.json", opts) do
replied_to_activities = get_replied_to_activities(opts.activities)
+ parallel = unless is_nil(opts[:parallel]), do: opts[:parallel], else: true
opts.activities
|> safe_render_many(
StatusView,
"status.json",
- Map.put(opts, :replied_to_activities, replied_to_activities)
+ Map.put(opts, :replied_to_activities, replied_to_activities),
+ parallel
)
end
@@ -88,6 +94,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged_activity =
Activity.create_by_object_ap_id(activity_object.data["id"])
|> Activity.with_preloaded_bookmark(opts[:for])
+ |> Activity.with_set_thread_muted_field(opts[:for])
|> Repo.one()
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
@@ -142,6 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
object = Object.normalize(activity)
user = get_user(activity.data["actor"])
+ user_follower_address = user.follower_address
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
@@ -157,7 +165,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
mentions =
(object.data["to"] ++ tag_mentions)
|> Enum.uniq()
- |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
+ |> Enum.map(fn
+ Pleroma.Constants.as_public() -> nil
+ ^user_follower_address -> nil
+ ap_id -> User.get_cached_by_ap_id(ap_id)
+ end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
@@ -168,7 +180,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
thread_muted? =
case activity.thread_muted? do
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
- nil -> CommonAPI.thread_muted?(user, activity)
+ nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
end
attachment_data = object.data["attachment"] || []
@@ -225,6 +237,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
object.data["url"] || object.data["external_url"] || object.data["id"]
end
+ direct_conversation_id =
+ with {_, true} <- {:include_id, opts[:with_direct_conversation_id]},
+ {_, %User{} = for_user} <- {:for_user, opts[:for]},
+ %{data: %{"context" => context}} when is_binary(context) <- activity,
+ %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
+ %Participation{id: participation_id} <-
+ Participation.for_user_and_conversation(for_user, conversation) do
+ participation_id
+ else
+ _e ->
+ nil
+ end
+
%{
id: to_string(activity.id),
uri: object.data["id"],
@@ -262,7 +287,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
conversation_id: get_context_id(activity),
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext},
- spoiler_text: %{"text/plain" => summary_plaintext}
+ spoiler_text: %{"text/plain" => summary_plaintext},
+ direct_conversation_id: direct_conversation_id
}
}
end
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 54f89e65c..ee14cfd6b 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -34,64 +34,18 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
def raw_nodeinfo do
stats = Stats.get_stats()
- exclusions = Config.get([:instance, :mrf_transparency_exclusions])
-
- mrf_simple =
- Config.get(:mrf_simple)
- |> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
- |> Enum.into(%{})
-
- # This horror is needed to convert regex sigils to strings
- mrf_keyword =
- Config.get(:mrf_keyword, [])
- |> Enum.map(fn {key, value} ->
- {key,
- Enum.map(value, fn
- {pattern, replacement} ->
- %{
- "pattern" =>
- if not is_binary(pattern) do
- inspect(pattern)
- else
- pattern
- end,
- "replacement" => replacement
- }
-
- pattern ->
- if not is_binary(pattern) do
- inspect(pattern)
- else
- pattern
- end
- end)}
- end)
- |> Enum.into(%{})
-
- mrf_policies =
- MRF.get_policies()
- |> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
-
quarantined = Config.get([:instance, :quarantined_instances], [])
staff_accounts =
User.all_superusers()
|> Enum.map(fn u -> u.ap_id end)
- mrf_user_allowlist =
- Config.get([:mrf_user_allowlist], [])
- |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
-
federation_response =
if Config.get([:instance, :mrf_transparency]) do
- %{
- mrf_policies: mrf_policies,
- mrf_simple: mrf_simple,
- mrf_keyword: mrf_keyword,
- mrf_user_allowlist: mrf_user_allowlist,
- quarantined_instances: quarantined,
- exclusions: length(exclusions) > 0
- }
+ {:ok, data} = MRF.describe()
+
+ data
+ |> Map.merge(%{quarantined_instances: quarantined})
else
%{}
end
diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex
index dca852449..f50098302 100644
--- a/lib/pleroma/web/oauth/token/clean_worker.ex
+++ b/lib/pleroma/web/oauth/token/clean_worker.ex
@@ -6,36 +6,30 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do
@moduledoc """
The module represents functions to clean an expired oauth tokens.
"""
+ use GenServer
+
+ @ten_seconds 10_000
+ @one_day 86_400_000
- # 10 seconds
- @start_interval 10_000
@interval Pleroma.Config.get(
- # 24 hours
[:oauth2, :clean_expired_tokens_interval],
- 86_400_000
+ @one_day
)
- @queue :background
alias Pleroma.Web.OAuth.Token
- def start_link, do: GenServer.start_link(__MODULE__, nil)
+ def start_link(_), do: GenServer.start_link(__MODULE__, %{})
def init(_) do
- if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do
- Process.send_after(self(), :perform, @start_interval)
- {:ok, nil}
- else
- :ignore
- end
+ Process.send_after(self(), :perform, @ten_seconds)
+ {:ok, nil}
end
@doc false
def handle_info(:perform, state) do
+ Token.delete_expired_tokens()
+
Process.send_after(self(), :perform, @interval)
- PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean])
{:noreply, state}
end
-
- # Job Worker Callbacks
- def perform(:clean), do: Token.delete_expired_tokens()
end
diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex
new file mode 100644
index 000000000..b6d2bf86b
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex
@@ -0,0 +1,73 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7]
+
+ alias Pleroma.Conversation.Participation
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.MastodonAPI.ConversationView
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
+ with %Participation{} = participation <- Participation.get(participation_id),
+ true <- user.id == participation.user_id do
+ conn
+ |> put_view(ConversationView)
+ |> render("participation.json", %{participation: participation, for: user})
+ end
+ end
+
+ def conversation_statuses(
+ %{assigns: %{user: user}} = conn,
+ %{"id" => participation_id} = params
+ ) do
+ params =
+ params
+ |> Map.put("blocking_user", user)
+ |> Map.put("muting_user", user)
+ |> Map.put("user", user)
+
+ participation =
+ participation_id
+ |> Participation.get(preload: [:conversation])
+
+ if user.id == participation.user_id do
+ activities =
+ participation.conversation.ap_id
+ |> ActivityPub.fetch_activities_for_context(params)
+ |> Enum.reverse()
+
+ conn
+ |> add_link_headers(
+ :conversation_statuses,
+ activities,
+ participation_id,
+ params,
+ nil,
+ &pleroma_api_url/4
+ )
+ |> put_view(StatusView)
+ |> render("index.json", %{activities: activities, for: user, as: :activity})
+ end
+ end
+
+ def update_conversation(
+ %{assigns: %{user: user}} = conn,
+ %{"id" => participation_id, "recipients" => recipients}
+ ) do
+ participation =
+ participation_id
+ |> Participation.get()
+
+ with true <- user.id == participation.user_id,
+ {:ok, participation} <- Participation.set_recipients(participation, recipients) do
+ conn
+ |> put_view(ConversationView)
+ |> render("participation.json", %{participation: participation, for: user})
+ end
+ end
+end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index c8c1c22dd..1eb6f7b9d 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -259,6 +259,21 @@ defmodule Pleroma.Web.Router do
end
end
+ scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
+ pipe_through(:authenticated_api)
+
+ scope [] do
+ pipe_through(:oauth_read)
+ get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
+ get("/conversations/:id", PleromaAPIController, :conversation)
+ end
+
+ scope [] do
+ pipe_through(:oauth_write)
+ patch("/conversations/:id", PleromaAPIController, :update_conversation)
+ end
+ end
+
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index 9ee331030..587c43f40 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.Streamer do
@keepalive_interval :timer.seconds(30)
- def start_link do
+ def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
@@ -35,28 +35,21 @@ defmodule Pleroma.Web.Streamer do
end
def init(args) do
- spawn(fn ->
- # 30 seconds
- Process.sleep(@keepalive_interval)
- GenServer.cast(__MODULE__, %{action: :ping})
- end)
+ Process.send_after(self(), %{action: :ping}, @keepalive_interval)
{:ok, args}
end
- def handle_cast(%{action: :ping}, topics) do
- Map.values(topics)
+ def handle_info(%{action: :ping}, topics) do
+ topics
+ |> Map.values()
|> List.flatten()
|> Enum.each(fn socket ->
Logger.debug("Sending keepalive ping")
send(socket.transport_pid, {:text, ""})
end)
- spawn(fn ->
- # 30 seconds
- Process.sleep(@keepalive_interval)
- GenServer.cast(__MODULE__, %{action: :ping})
- end)
+ Process.send_after(self(), %{action: :ping}, @keepalive_interval)
{:noreply, topics}
end
@@ -120,8 +113,7 @@ defmodule Pleroma.Web.Streamer do
|> Map.get("#{topic}:#{item.user_id}", [])
|> Enum.each(fn socket ->
with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id),
- true <- should_send?(user, item),
- false <- CommonAPI.thread_muted?(user, item.activity) do
+ true <- should_send?(user, item) do
send(
socket.transport_pid,
{:text, represent_notification(socket.assigns[:user], item)}
@@ -209,7 +201,7 @@ defmodule Pleroma.Web.Streamer do
payload:
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{
participation: participation,
- user: participation.user
+ for: participation.user
})
|> Jason.encode!()
}
@@ -243,7 +235,8 @@ defmodule Pleroma.Web.Streamer do
%{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
- true <- thread_containment(item, user) do
+ true <- thread_containment(item, user),
+ false <- CommonAPI.thread_muted?(user, item) do
true
else
_ -> false
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index 8d8892068..8a7d2fc72 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -74,12 +74,15 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|> HTML.filter_tags(User.html_filter_policy(for_user))
|> Formatter.emojify(emoji)
- # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
- # For example: [{"name": "Pronoun", "value": "she/her"}, …]
fields =
- (user.info.source_data["attachment"] || [])
- |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
- |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+ user.info
+ |> User.Info.fields()
+ |> Enum.map(fn %{"name" => name, "value" => value} ->
+ %{
+ "name" => Pleroma.HTML.strip_tags(name),
+ "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
+ }
+ end)
data =
%{
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
index b42f6887e..bfb6c7287 100644
--- a/lib/pleroma/web/web.ex
+++ b/lib/pleroma/web/web.ex
@@ -58,17 +58,31 @@ defmodule Pleroma.Web do
rescue
error ->
Logger.error(
- "#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}"
+ "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
+ Exception.format(:error, error, __STACKTRACE__)
)
- Logger.error(inspect(__STACKTRACE__))
nil
end
@doc """
- Same as `render_many/4` but wrapped in rescue block.
+ Same as `render_many/4` but wrapped in rescue block and parallelized (unless disabled by passing false as a fifth argument).
"""
- def safe_render_many(collection, view, template, assigns \\ %{}) do
+ def safe_render_many(collection, view, template, assigns \\ %{}, parallel \\ true)
+
+ def safe_render_many(collection, view, template, assigns, true) do
+ Enum.map(collection, fn resource ->
+ Task.async(fn ->
+ as = Map.get(assigns, :as) || view.__resource__
+ assigns = Map.put(assigns, as, resource)
+ safe_render(view, template, assigns)
+ end)
+ end)
+ |> Enum.map(&Task.await(&1, :infinity))
+ |> Enum.filter(& &1)
+ end
+
+ def safe_render_many(collection, view, template, assigns, false) do
Enum.map(collection, fn resource ->
as = Map.get(assigns, :as) || view.__resource__
assigns = Map.put(assigns, as, resource)