summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/email.ex5
-rw-r--r--lib/pleroma/activity.ex2
-rw-r--r--lib/pleroma/activity_expiration.ex2
-rw-r--r--lib/pleroma/application.ex13
-rw-r--r--lib/pleroma/config/loader.ex6
-rw-r--r--lib/pleroma/config/transfer_task.ex93
-rw-r--r--lib/pleroma/daemons/activity_expiration_daemon.ex66
-rw-r--r--lib/pleroma/daemons/digest_email_daemon.ex42
-rw-r--r--lib/pleroma/daemons/scheduled_activity_daemon.ex62
-rw-r--r--lib/pleroma/docs/generator.ex2
-rw-r--r--lib/pleroma/emails/new_users_digest_email.ex32
-rw-r--r--lib/pleroma/following_relationship.ex2
-rw-r--r--lib/pleroma/formatter.ex3
-rw-r--r--lib/pleroma/html.ex1
-rw-r--r--lib/pleroma/notification.ex4
-rw-r--r--lib/pleroma/object.ex13
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex47
-rw-r--r--lib/pleroma/plugs/parsers_plug.ex21
-rw-r--r--lib/pleroma/plugs/rate_limiter/rate_limiter.ex37
-rw-r--r--lib/pleroma/plugs/remote_ip.ex3
-rw-r--r--lib/pleroma/scheduled_activity.ex83
-rw-r--r--lib/pleroma/scheduler.ex7
-rw-r--r--lib/pleroma/stats.ex39
-rw-r--r--lib/pleroma/user.ex43
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex6
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex1
-rw-r--r--lib/pleroma/web/activity_pub/mrf/object_age_policy.ex3
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf/subchain_policy.ex2
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex8
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex13
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex146
-rw-r--r--lib/pleroma/web/admin_api/views/config_view.ex10
-rw-r--r--lib/pleroma/web/common_api/common_api.ex3
-rw-r--r--lib/pleroma/web/common_api/utils.ex14
-rw-r--r--lib/pleroma/web/endpoint.ex12
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex19
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/views/poll_view.ex3
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex35
-rw-r--r--lib/pleroma/web/metadata/rel_me.ex6
-rw-r--r--lib/pleroma/web/nodeinfo/nodeinfo_controller.ex2
-rw-r--r--lib/pleroma/web/oauth/token/clean_worker.ex34
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex12
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex11
-rw-r--r--lib/pleroma/web/rel_me.ex5
-rw-r--r--lib/pleroma/web/rich_media/parser.ex6
-rw-r--r--lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex2
-rw-r--r--lib/pleroma/web/router.ex8
-rw-r--r--lib/pleroma/web/streamer/worker.ex3
-rw-r--r--lib/pleroma/web/templates/email/new_users_digest.html.eex158
-rw-r--r--lib/pleroma/web/templates/layout/email_styled.html.eex193
-rw-r--r--lib/pleroma/web/views/email_view.ex4
-rw-r--r--lib/pleroma/workers/activity_expiration_worker.ex18
-rw-r--r--lib/pleroma/workers/background_worker.ex5
-rw-r--r--lib/pleroma/workers/cron/clear_oauth_token_worker.ex21
-rw-r--r--lib/pleroma/workers/cron/digest_emails_worker.ex58
-rw-r--r--lib/pleroma/workers/cron/new_users_digest_worker.ex60
-rw-r--r--lib/pleroma/workers/cron/purge_expired_activities_worker.ex46
-rw-r--r--lib/pleroma/workers/cron/stats_worker.ex16
-rw-r--r--lib/pleroma/workers/digest_emails_worker.ex16
-rw-r--r--lib/pleroma/workers/scheduled_activity_worker.ex36
62 files changed, 1110 insertions, 519 deletions
diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex
index 2c3801429..d3fac6ec8 100644
--- a/lib/mix/tasks/pleroma/email.ex
+++ b/lib/mix/tasks/pleroma/email.ex
@@ -1,5 +1,6 @@
defmodule Mix.Tasks.Pleroma.Email do
use Mix.Task
+ import Mix.Pleroma
@shortdoc "Simple Email test"
@moduledoc File.read!("docs/administration/CLI_tasks/email.md")
@@ -18,8 +19,6 @@ defmodule Mix.Tasks.Pleroma.Email do
email = Pleroma.Emails.AdminEmail.test_email(options[:to])
{:ok, _} = Pleroma.Emails.Mailer.deliver(email)
- Mix.shell().info(
- "Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}"
- )
+ shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
end
end
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 0f8fce774..72e2256ea 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -31,7 +31,7 @@ defmodule Pleroma.Activity do
"Announce" => "reblog",
"Like" => "favourite",
"Move" => "move",
- "EmojiReaction" => "pleroma:emoji_reaction"
+ "EmojiReact" => "pleroma:emoji_reaction"
}
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex
index 7ea5c48ca..a58a493f7 100644
--- a/lib/pleroma/activity_expiration.ex
+++ b/lib/pleroma/activity_expiration.ex
@@ -62,6 +62,6 @@ defmodule Pleroma.ActivityExpiration do
def expires_late_enough?(scheduled_at) do
now = NaiveDateTime.utc_now()
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
- diff >= @min_activity_lifetime
+ diff > @min_activity_lifetime
end
end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index e17068876..27758cf94 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -33,6 +33,7 @@ defmodule Pleroma.Application do
def start(_type, _args) do
Pleroma.HTML.compile_scrubbers()
Pleroma.Config.DeprecationWarnings.warn()
+ Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
Pleroma.Repo.check_migrations_applied!()
setup_instrumenters()
load_custom_modules()
@@ -41,12 +42,9 @@ defmodule Pleroma.Application do
children =
[
Pleroma.Repo,
- Pleroma.Scheduler,
Pleroma.Config.TransferTask,
Pleroma.Emoji,
Pleroma.Captcha,
- Pleroma.Daemons.ScheduledActivityDaemon,
- Pleroma.Daemons.ActivityExpirationDaemon,
Pleroma.Plugs.RateLimiter.Supervisor
] ++
cachex_children() ++
@@ -57,7 +55,6 @@ defmodule Pleroma.Application do
{Oban, Pleroma.Config.get(Oban)}
] ++
task_children(@env) ++
- oauth_cleanup_child(oauth_cleanup_enabled?()) ++
streamer_child(@env) ++
chat_child(@env, chat_enabled?()) ++
[
@@ -159,20 +156,12 @@ defmodule Pleroma.Application do
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.supervisor()]
end
- defp oauth_cleanup_child(true),
- do: [Pleroma.Web.OAuth.Token.CleanWorker]
-
- defp oauth_cleanup_child(_), do: []
-
defp chat_child(_env, true) do
[Pleroma.Web.ChatChannel.ChatChannelState]
end
diff --git a/lib/pleroma/config/loader.ex b/lib/pleroma/config/loader.ex
index 68b247381..b8787cb49 100644
--- a/lib/pleroma/config/loader.ex
+++ b/lib/pleroma/config/loader.ex
@@ -3,8 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Loader do
- @paths ["config/config.exs", "config/#{Mix.env()}.exs"]
-
@reject_keys [
Pleroma.Repo,
Pleroma.Web.Endpoint,
@@ -35,8 +33,8 @@ defmodule Pleroma.Config.Loader do
def load_and_merge do
all_paths =
if Pleroma.Config.get(:release),
- do: @paths ++ ["config/releases.exs"],
- else: @paths
+ do: ["config/config.exs", "config/releases.exs"],
+ else: ["config/config.exs"]
all_paths
|> Enum.map(&load(&1))
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index d54f38ee4..f037ce8a5 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -10,6 +10,30 @@ defmodule Pleroma.Config.TransferTask do
require Logger
+ @type env() :: :test | :benchmark | :dev | :prod
+
+ @reboot_time_keys [
+ {:pleroma, :hackney_pools},
+ {:pleroma, :chat},
+ {:pleroma, Oban},
+ {:pleroma, :rate_limit},
+ {:pleroma, :markup},
+ {:plerome, :streamer}
+ ]
+
+ @reboot_time_subkeys [
+ {:pleroma, Pleroma.Captcha, [:seconds_valid]},
+ {:pleroma, Pleroma.Upload, [:proxy_remote]},
+ {:pleroma, :instance, [:upload_limit]},
+ {:pleroma, :email_notifications, [:digest]},
+ {:pleroma, :oauth2, [:clean_expired_tokens]},
+ {:pleroma, Pleroma.ActivityExpiration, [:enabled]},
+ {:pleroma, Pleroma.ScheduledActivity, [:enabled]},
+ {:pleroma, :gopher, [:enabled]}
+ ]
+
+ @reject [nil, :prometheus]
+
def start_link(_) do
load_and_update_env()
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
@@ -17,21 +41,34 @@ defmodule Pleroma.Config.TransferTask do
end
@spec load_and_update_env([ConfigDB.t()]) :: :ok | false
- def load_and_update_env(deleted \\ []) do
+ def load_and_update_env(deleted \\ [], restart_pleroma? \\ true) do
with true <- Pleroma.Config.get(:configurable_from_database),
true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"),
started_applications <- Application.started_applications() do
# We need to restart applications for loaded settings take effect
+
in_db = Repo.all(ConfigDB)
with_deleted = in_db ++ deleted
- with_deleted
- |> Enum.map(&merge_and_update(&1))
- |> Enum.uniq()
- # TODO: some problem with prometheus after restart!
- |> Enum.reject(&(&1 in [:pleroma, nil, :prometheus]))
- |> Enum.each(&restart(started_applications, &1))
+ reject_for_restart = if restart_pleroma?, do: @reject, else: [:pleroma | @reject]
+
+ applications =
+ with_deleted
+ |> Enum.map(&merge_and_update(&1))
+ |> Enum.uniq()
+ # TODO: some problem with prometheus after restart!
+ |> Enum.reject(&(&1 in reject_for_restart))
+
+ # to be ensured that pleroma will be restarted last
+ applications =
+ if :pleroma in applications do
+ List.delete(applications, :pleroma) ++ [:pleroma]
+ else
+ applications
+ end
+
+ Enum.each(applications, &restart(started_applications, &1, Pleroma.Config.get(:env)))
:ok
end
@@ -43,12 +80,25 @@ defmodule Pleroma.Config.TransferTask do
group = ConfigDB.from_string(setting.group)
default = Pleroma.Config.Holder.config(group, key)
- merged_value = merge_value(setting, default, group, key)
+ value = ConfigDB.from_binary(setting.value)
+
+ merged_value =
+ if Ecto.get_meta(setting, :state) == :deleted do
+ default
+ else
+ if can_be_merged?(default, value) do
+ ConfigDB.merge_group(group, key, default, value)
+ else
+ value
+ end
+ end
:ok = update_env(group, key, merged_value)
if group != :logger do
- group
+ if group != :pleroma or pleroma_need_restart?(group, key, value) do
+ group
+ end
else
# change logger configuration in runtime, without restart
if Keyword.keyword?(merged_value) and
@@ -76,22 +126,29 @@ defmodule Pleroma.Config.TransferTask do
end
end
- defp merge_value(%{__meta__: %{state: :deleted}}, default, _group, _key), do: default
+ @spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
+ def pleroma_need_restart?(group, key, value) do
+ group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
+ end
- defp merge_value(setting, default, group, key) do
- value = ConfigDB.from_binary(setting.value)
+ defp group_and_key_need_reboot?(group, key) do
+ Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
+ end
- if can_be_merged?(default, value) do
- ConfigDB.merge_group(group, key, default, value)
- else
- value
- end
+ defp group_and_subkey_need_reboot?(group, key, value) do
+ Keyword.keyword?(value) and
+ Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
+ g == group and k == key and
+ Enum.any?(Keyword.keys(value), &(&1 in subkeys))
+ end)
end
defp update_env(group, key, nil), do: Application.delete_env(group, key)
defp update_env(group, key, value), do: Application.put_env(group, key, value)
- defp restart(started_applications, app) do
+ defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
+
+ defp restart(started_applications, app, _) do
with {^app, _, _} <- List.keyfind(started_applications, app, 0),
:ok <- Application.stop(app) do
:ok = Application.start(app)
diff --git a/lib/pleroma/daemons/activity_expiration_daemon.ex b/lib/pleroma/daemons/activity_expiration_daemon.ex
deleted file mode 100644
index cab7628c4..000000000
--- a/lib/pleroma/daemons/activity_expiration_daemon.ex
+++ /dev/null
@@ -1,66 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Daemons.ActivityExpirationDaemon do
- alias Pleroma.Activity
- alias Pleroma.ActivityExpiration
- alias Pleroma.Config
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI
-
- require Logger
- use GenServer
- import Ecto.Query
-
- @schedule_interval :timer.minutes(1)
-
- def start_link(_) do
- GenServer.start_link(__MODULE__, nil)
- end
-
- @impl true
- def init(_) do
- if Config.get([ActivityExpiration, :enabled]) do
- schedule_next()
- {:ok, nil}
- else
- :ignore
- end
- end
-
- def perform(:execute, expiration_id) do
- try do
- expiration =
- ActivityExpiration
- |> where([e], e.id == ^expiration_id)
- |> Repo.one!()
-
- activity = Activity.get_by_id_with_object(expiration.activity_id)
- user = User.get_by_ap_id(activity.object.data["actor"])
- CommonAPI.delete(activity.id, user)
- rescue
- error ->
- Logger.error("#{__MODULE__} Couldn't delete expired activity: #{inspect(error)}")
- end
- end
-
- @impl true
- def handle_info(:perform, state) do
- ActivityExpiration.due_expirations(@schedule_interval)
- |> Enum.each(fn expiration ->
- Pleroma.Workers.ActivityExpirationWorker.enqueue(
- "activity_expiration",
- %{"activity_expiration_id" => expiration.id}
- )
- end)
-
- schedule_next()
- {:noreply, state}
- end
-
- defp schedule_next do
- Process.send_after(self(), :perform, @schedule_interval)
- end
-end
diff --git a/lib/pleroma/daemons/digest_email_daemon.ex b/lib/pleroma/daemons/digest_email_daemon.ex
deleted file mode 100644
index b4c8eaad9..000000000
--- a/lib/pleroma/daemons/digest_email_daemon.ex
+++ /dev/null
@@ -1,42 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Daemons.DigestEmailDaemon do
- alias Pleroma.Repo
- alias Pleroma.Workers.DigestEmailsWorker
-
- import Ecto.Query
-
- def perform do
- config = Pleroma.Config.get([:email_notifications, :digest])
- negative_interval = -Map.fetch!(config, :interval)
- inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
- inactive_users_query = Pleroma.User.list_inactive_users_query(inactivity_threshold)
-
- now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
-
- from(u in inactive_users_query,
- where: fragment(~s(? ->'digest' @> 'true'), u.email_notifications),
- where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
- select: u
- )
- |> Repo.all()
- |> Enum.each(fn user ->
- DigestEmailsWorker.enqueue("digest_email", %{"user_id" => user.id})
- end)
- end
-
- @doc """
- Send digest email to the given user.
- Updates `last_digest_emailed_at` field for the user and returns the updated user.
- """
- @spec perform(Pleroma.User.t()) :: Pleroma.User.t()
- def perform(user) do
- with %Swoosh.Email{} = email <- Pleroma.Emails.UserEmail.digest_email(user) do
- Pleroma.Emails.Mailer.deliver_async(email)
- end
-
- Pleroma.User.touch_last_digest_emailed_at(user)
- end
-end
diff --git a/lib/pleroma/daemons/scheduled_activity_daemon.ex b/lib/pleroma/daemons/scheduled_activity_daemon.ex
deleted file mode 100644
index aee5f723a..000000000
--- a/lib/pleroma/daemons/scheduled_activity_daemon.ex
+++ /dev/null
@@ -1,62 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Daemons.ScheduledActivityDaemon do
- @moduledoc """
- Sends scheduled activities to the job queue.
- """
-
- alias Pleroma.Config
- alias Pleroma.ScheduledActivity
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI
-
- use GenServer
- require Logger
-
- @schedule_interval :timer.minutes(1)
-
- def start_link(_) do
- GenServer.start_link(__MODULE__, nil)
- end
-
- def init(_) do
- if Config.get([ScheduledActivity, :enabled]) do
- schedule_next()
- {:ok, nil}
- else
- :ignore
- end
- end
-
- def perform(:execute, scheduled_activity_id) do
- try do
- {:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id)
- %User{} = user = User.get_cached_by_id(scheduled_activity.user_id)
- {:ok, _result} = CommonAPI.post(user, scheduled_activity.params)
- rescue
- error ->
- Logger.error(
- "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
- )
- end
- end
-
- def handle_info(:perform, state) do
- ScheduledActivity.due_activities(@schedule_interval)
- |> Enum.each(fn scheduled_activity ->
- Pleroma.Workers.ScheduledActivityWorker.enqueue(
- "execute",
- %{"activity_id" => scheduled_activity.id}
- )
- end)
-
- schedule_next()
- {:noreply, state}
- end
-
- defp schedule_next do
- Process.send_after(self(), :perform, @schedule_interval)
- end
-end
diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex
index 6b12dcdd9..e0fc8cd02 100644
--- a/lib/pleroma/docs/generator.ex
+++ b/lib/pleroma/docs/generator.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Docs.Generator do
|> Enum.filter(&String.ends_with?(&1, ".ex"))
|> Enum.map(fn filename ->
module = filename |> String.trim_trailing(".ex") |> Macro.camelize()
- String.to_existing_atom(start <> module)
+ String.to_atom(start <> module)
end)
end
end
diff --git a/lib/pleroma/emails/new_users_digest_email.ex b/lib/pleroma/emails/new_users_digest_email.ex
new file mode 100644
index 000000000..7d16b807f
--- /dev/null
+++ b/lib/pleroma/emails/new_users_digest_email.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Emails.NewUsersDigestEmail do
+ use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email_styled}
+
+ defp instance_notify_email do
+ Pleroma.Config.get([:instance, :notify_email]) || Pleroma.Config.get([:instance, :email])
+ end
+
+ def new_users(to, users_and_statuses) do
+ instance_name = Pleroma.Config.get([:instance, :name])
+ styling = Pleroma.Config.get([Pleroma.Emails.UserEmail, :styling])
+
+ logo_url =
+ Pleroma.Web.Endpoint.url() <>
+ Pleroma.Config.get([:frontend_configurations, :pleroma_fe, :logo])
+
+ new()
+ |> to({to.name, to.email})
+ |> from({instance_name, instance_notify_email()})
+ |> subject("#{instance_name} New Users")
+ |> render_body("new_users_digest.html", %{
+ title: "New Users",
+ users_and_statuses: users_and_statuses,
+ instance: instance_name,
+ styling: styling,
+ logo_url: logo_url
+ })
+ end
+end
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
index 0b0219b82..b8cb3bf03 100644
--- a/lib/pleroma/following_relationship.ex
+++ b/lib/pleroma/following_relationship.ex
@@ -58,8 +58,8 @@ defmodule Pleroma.FollowingRelationship do
def unfollow(%User{} = follower, %User{} = following) do
case get(follower, following) do
- nil -> {:ok, nil}
%__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
+ _ -> {:ok, nil}
end
end
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 19b9af46c..90895374d 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -13,7 +13,8 @@ defmodule Pleroma.Formatter do
@auto_linker_config hashtag: true,
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
mention: true,
- mention_handler: &Pleroma.Formatter.mention_handler/4
+ mention_handler: &Pleroma.Formatter.mention_handler/4,
+ scheme: true
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
case User.get_cached_by_nickname(nickname) do
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 11513106e..05946aa96 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -108,6 +108,7 @@ defmodule Pleroma.HTML do
Cachex.fetch!(:scrubber_cache, key, fn _key ->
result =
content
+ |> Floki.parse_fragment!()
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|> Floki.attribute("a", "href")
|> Enum.at(0)
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index d04a65a1e..66e91fcef 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -294,7 +294,7 @@ defmodule Pleroma.Notification do
end
def create_notifications(%Activity{data: %{"type" => type}} = activity)
- when type in ["Like", "Announce", "Follow", "Move", "EmojiReaction"] do
+ when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
notifications =
activity
|> get_notified_from_activity()
@@ -322,7 +322,7 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(activity, local_only \\ true)
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
- when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReaction"] do
+ when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReact"] do
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 38e372f6d..52556bf31 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -184,11 +184,14 @@ defmodule Pleroma.Object do
with {:ok, _obj} = swap_object_with_tombstone(object),
deleted_activity = Activity.delete_all_by_object_ap_id(id),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
- {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path),
- {:ok, _} <-
- Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
- "object" => object
- }) do
+ {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
+ with true <- Pleroma.Config.get([:instance, :cleanup_attachments]) do
+ {:ok, _} =
+ Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{
+ "object" => object
+ })
+ end
+
{:ok, object, deleted_activity}
end
end
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index a7cc22831..b04273979 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -6,6 +6,8 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
alias Pleroma.Config
import Plug.Conn
+ require Logger
+
def init(opts), do: opts
def call(conn, _options) do
@@ -90,6 +92,51 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|> Enum.join("; ")
end
+ def warn_if_disabled do
+ unless Config.get([:http_security, :enabled]) do
+ Logger.warn("
+ .i;;;;i.
+ iYcviii;vXY:
+ .YXi .i1c.
+ .YC. . in7.
+ .vc. ...... ;1c.
+ i7, .. .;1;
+ i7, .. ... .Y1i
+ ,7v .6MMM@; .YX,
+ .7;. ..IMMMMMM1 :t7.
+ .;Y. ;$MMMMMM9. :tc.
+ vY. .. .nMMM@MMU. ;1v.
+ i7i ... .#MM@M@C. .....:71i
+ it: .... $MMM@9;.,i;;;i,;tti
+ :t7. ..... 0MMMWv.,iii:::,,;St.
+ .nC. ..... IMMMQ..,::::::,.,czX.
+ .ct: ....... .ZMMMI..,:::::::,,:76Y.
+ c2: ......,i..Y$M@t..:::::::,,..inZY
+ vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
+ i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
+ iIS. ......:ii::..;@MI....,............;Ez.
+ .I9. ......:i::::...8M1..................C0z.
+ .z9; ......:i::::,.. .i:...................zWX.
+ vbv ......,i::::,,. ................. :AQY
+ c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
+ :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
+ :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
+ .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
+ 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
+ 7C...::::::::::::,,,,.. .................... vSi.
+ ;1;...,,::::::,......... .................. Yz:
+ v97,......... .voC.
+ izAotX7777777777777777777777777777777777777777Y7n92:
+ .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
+
+HTTP Security is disabled. Please re-enable it to prevent users from attacking
+your instance and your users via malicious posts:
+
+ config :pleroma, :http_security, enabled: true
+ ")
+ end
+ end
+
defp maybe_send_sts_header(conn, true) do
max_age_sts = Config.get([:http_security, :sts_max_age])
max_age_ct = Config.get([:http_security, :ct_max_age])
diff --git a/lib/pleroma/plugs/parsers_plug.ex b/lib/pleroma/plugs/parsers_plug.ex
deleted file mode 100644
index 2e493ce0e..000000000
--- a/lib/pleroma/plugs/parsers_plug.ex
+++ /dev/null
@@ -1,21 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.Parsers do
- @moduledoc "Initializes Plug.Parsers with upload limit set at boot time"
-
- @behaviour Plug
-
- def init(_opts) do
- Plug.Parsers.init(
- parsers: [:urlencoded, :multipart, :json],
- pass: ["*/*"],
- json_decoder: Jason,
- length: Pleroma.Config.get([:instance, :upload_limit]),
- body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
- )
- end
-
- defdelegate call(conn, opts), to: Plug.Parsers
-end
diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex
index d720508c8..7fb92489c 100644
--- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex
+++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex
@@ -67,6 +67,8 @@ defmodule Pleroma.Plugs.RateLimiter do
alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
alias Pleroma.User
+ require Logger
+
def init(opts) do
limiter_name = Keyword.get(opts, :name)
@@ -89,18 +91,39 @@ defmodule Pleroma.Plugs.RateLimiter do
def call(conn, nil), do: conn
def call(conn, settings) do
- settings
- |> incorporate_conn_info(conn)
- |> check_rate()
- |> case do
- {:ok, _count} ->
+ case disabled?() do
+ true ->
+ if Pleroma.Config.get(:env) == :prod,
+ do: Logger.warn("Rate limiter is disabled for localhost/socket")
+
conn
- {:error, _count} ->
- render_throttled_error(conn)
+ false ->
+ settings
+ |> incorporate_conn_info(conn)
+ |> check_rate()
+ |> case do
+ {:ok, _count} ->
+ conn
+
+ {:error, _count} ->
+ render_throttled_error(conn)
+ end
end
end
+ def disabled? do
+ localhost_or_socket =
+ Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip])
+ |> Tuple.to_list()
+ |> Enum.join(".")
+ |> String.match?(~r/^local|^127.0.0.1/)
+
+ remote_ip_disabled = not Pleroma.Config.get([Pleroma.Plugs.RemoteIp, :enabled])
+
+ localhost_or_socket and remote_ip_disabled
+ end
+
def inspect_bucket(conn, name_root, settings) do
settings =
settings
diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex
index fdedc27ee..1cd5af48a 100644
--- a/lib/pleroma/plugs/remote_ip.ex
+++ b/lib/pleroma/plugs/remote_ip.ex
@@ -10,10 +10,7 @@ defmodule Pleroma.Plugs.RemoteIp do
@behaviour Plug
@headers ~w[
- forwarded
x-forwarded-for
- x-client-ip
- x-real-ip
]
# https://en.wikipedia.org/wiki/Localhost
diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex
index fea2cf3ff..e81bfcd7d 100644
--- a/lib/pleroma/scheduled_activity.ex
+++ b/lib/pleroma/scheduled_activity.ex
@@ -5,15 +5,19 @@
defmodule Pleroma.ScheduledActivity do
use Ecto.Schema
+ alias Ecto.Multi
alias Pleroma.Config
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Workers.ScheduledActivityWorker
import Ecto.Query
import Ecto.Changeset
+ @type t :: %__MODULE__{}
+
@min_offset :timer.minutes(5)
schema "scheduled_activities" do
@@ -105,16 +109,32 @@ defmodule Pleroma.ScheduledActivity do
end
def new(%User{} = user, attrs) do
- %ScheduledActivity{user_id: user.id}
- |> changeset(attrs)
+ changeset(%ScheduledActivity{user_id: user.id}, attrs)
end
+ @doc """
+ Creates ScheduledActivity and add to queue to perform at scheduled_at date
+ """
+ @spec create(User.t(), map()) :: {:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
def create(%User{} = user, attrs) do
- user
- |> new(attrs)
- |> Repo.insert()
+ Multi.new()
+ |> Multi.insert(:scheduled_activity, new(user, attrs))
+ |> maybe_add_jobs(Config.get([ScheduledActivity, :enabled]))
+ |> Repo.transaction()
+ |> transaction_response
+ end
+
+ defp maybe_add_jobs(multi, true) do
+ multi
+ |> Multi.run(:scheduled_activity_job, fn _repo, %{scheduled_activity: activity} ->
+ %{activity_id: activity.id}
+ |> ScheduledActivityWorker.new(scheduled_at: activity.scheduled_at)
+ |> Oban.insert()
+ end)
end
+ defp maybe_add_jobs(multi, _), do: multi
+
def get(%User{} = user, scheduled_activity_id) do
ScheduledActivity
|> where(user_id: ^user.id)
@@ -122,25 +142,43 @@ defmodule Pleroma.ScheduledActivity do
|> Repo.one()
end
- def update(%ScheduledActivity{} = scheduled_activity, attrs) do
- scheduled_activity
- |> update_changeset(attrs)
- |> Repo.update()
+ @spec update(ScheduledActivity.t(), map()) ::
+ {:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
+ def update(%ScheduledActivity{id: id} = scheduled_activity, attrs) do
+ with {:error, %Ecto.Changeset{valid?: true} = changeset} <-
+ {:error, update_changeset(scheduled_activity, attrs)} do
+ Multi.new()
+ |> Multi.update(:scheduled_activity, changeset)
+ |> Multi.update_all(:scheduled_job, job_query(id),
+ set: [scheduled_at: get_field(changeset, :scheduled_at)]
+ )
+ |> Repo.transaction()
+ |> transaction_response
+ end
end
- def delete(%ScheduledActivity{} = scheduled_activity) do
- scheduled_activity
- |> Repo.delete()
+ @doc "Deletes a ScheduledActivity and linked jobs."
+ @spec delete(ScheduledActivity.t() | binary() | integer) ::
+ {:ok, ScheduledActivity.t()} | {:error, Ecto.Changeset.t()}
+ def delete(%ScheduledActivity{id: id} = scheduled_activity) do
+ Multi.new()
+ |> Multi.delete(:scheduled_activity, scheduled_activity, stale_error_field: :id)
+ |> Multi.delete_all(:jobs, job_query(id))
+ |> Repo.transaction()
+ |> transaction_response
end
def delete(id) when is_binary(id) or is_integer(id) do
- ScheduledActivity
- |> where(id: ^id)
- |> select([sa], sa)
- |> Repo.delete_all()
- |> case do
- {1, [scheduled_activity]} -> {:ok, scheduled_activity}
- _ -> :error
+ delete(%__MODULE__{id: id})
+ end
+
+ defp transaction_response(result) do
+ case result do
+ {:ok, %{scheduled_activity: scheduled_activity}} ->
+ {:ok, scheduled_activity}
+
+ {:error, _, changeset, _} ->
+ {:error, changeset}
end
end
@@ -158,4 +196,11 @@ defmodule Pleroma.ScheduledActivity do
|> where([sa], sa.scheduled_at < ^naive_datetime)
|> Repo.all()
end
+
+ def job_query(scheduled_activity_id) do
+ from(j in Oban.Job,
+ where: j.queue == "scheduled_activities",
+ where: fragment("args ->> 'activity_id' = ?::text", ^to_string(scheduled_activity_id))
+ )
+ end
end
diff --git a/lib/pleroma/scheduler.ex b/lib/pleroma/scheduler.ex
deleted file mode 100644
index d84cd99ad..000000000
--- a/lib/pleroma/scheduler.ex
+++ /dev/null
@@ -1,7 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Scheduler do
- use Quantum.Scheduler, otp_app: :pleroma
-end
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 8154a09b7..cf590fb01 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -9,22 +9,43 @@ defmodule Pleroma.Stats do
use GenServer
- @interval 1000 * 60 * 60
+ @init_state %{
+ peers: [],
+ stats: %{
+ domain_count: 0,
+ status_count: 0,
+ user_count: 0
+ }
+ }
def start_link(_) do
- GenServer.start_link(__MODULE__, initial_data(), name: __MODULE__)
+ GenServer.start_link(
+ __MODULE__,
+ @init_state,
+ name: __MODULE__
+ )
end
+ @doc "Performs update stats"
def force_update do
GenServer.call(__MODULE__, :force_update)
end
+ @doc "Performs collect stats"
+ def do_collect do
+ GenServer.cast(__MODULE__, :run_update)
+ end
+
+ @doc "Returns stats data"
+ @spec get_stats() :: %{domain_count: integer(), status_count: integer(), user_count: integer()}
def get_stats do
%{stats: stats} = GenServer.call(__MODULE__, :get_state)
stats
end
+ @doc "Returns list peers"
+ @spec get_peers() :: list(String.t())
def get_peers do
%{peers: peers} = GenServer.call(__MODULE__, :get_state)
@@ -32,7 +53,6 @@ defmodule Pleroma.Stats do
end
def init(args) do
- Process.send(self(), :run_update, [])
{:ok, args}
end
@@ -45,17 +65,12 @@ defmodule Pleroma.Stats do
{:reply, state, state}
end
- def handle_info(:run_update, _state) do
+ def handle_cast(:run_update, _state) do
new_stats = get_stat_data()
- Process.send_after(self(), :run_update, @interval)
{:noreply, new_stats}
end
- defp initial_data do
- %{peers: [], stats: %{}}
- end
-
defp get_stat_data do
peers =
from(
@@ -74,7 +89,11 @@ defmodule Pleroma.Stats do
%{
peers: peers,
- stats: %{domain_count: domain_count, status_count: status_count, user_count: user_count}
+ stats: %{
+ domain_count: domain_count,
+ status_count: status_count,
+ user_count: user_count
+ }
}
end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 3c86cdb38..5ea36fea3 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -647,25 +647,48 @@ defmodule Pleroma.User do
end
end
+ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
+ {:error, "Not subscribed!"}
+ end
+
def unfollow(%User{} = follower, %User{} = followed) do
- if following?(follower, followed) and follower.ap_id != followed.ap_id do
- FollowingRelationship.unfollow(follower, followed)
+ case get_follow_state(follower, followed) do
+ state when state in ["accept", "pending"] ->
+ FollowingRelationship.unfollow(follower, followed)
+ {:ok, followed} = update_follower_count(followed)
- {:ok, followed} = update_follower_count(followed)
+ {:ok, follower} =
+ follower
+ |> update_following_count()
+ |> set_cache()
- {:ok, follower} =
- follower
- |> update_following_count()
- |> set_cache()
+ {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
- {:ok, follower, Utils.fetch_latest_follow(follower, followed)}
- else
- {:error, "Not subscribed!"}
+ nil ->
+ {:error, "Not subscribed!"}
end
end
defdelegate following?(follower, followed), to: FollowingRelationship
+ def get_follow_state(%User{} = follower, %User{} = following) do
+ following_relationship = FollowingRelationship.get(follower, following)
+
+ case {following_relationship, following.local} do
+ {nil, false} ->
+ case Utils.fetch_latest_follow(follower, following) do
+ %{data: %{"state" => state}} when state in ["pending", "accept"] -> state
+ _ -> nil
+ end
+
+ {%{state: state}, _} ->
+ state
+
+ {nil, _} ->
+ nil
+ end
+ end
+
def locked?(%User{} = user) do
user.locked || false
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 1ac67b618..5c436941a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -325,12 +325,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def react_with_emoji(user, object, emoji, options \\ []) do
with local <- Keyword.get(options, :local, true),
activity_id <- Keyword.get(options, :activity_id, nil),
- Pleroma.Emoji.is_unicode_emoji?(emoji),
+ true <- Pleroma.Emoji.is_unicode_emoji?(emoji),
reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id),
{:ok, activity} <- insert(reaction_data, local),
{:ok, object} <- add_emoji_reaction_to_object(activity, object),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
+ else
+ e -> {:error, e}
end
end
@@ -345,6 +347,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
+ else
+ e -> {:error, e}
end
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 8abe18e29..802d10edc 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
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
# does the post contain links?
defp contains_links?(%{"content" => content} = _object) do
content
+ |> Floki.parse_fragment!()
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl")
|> Floki.attribute("a", "href")
|> length() > 0
diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
index 8b36c1021..788508349 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -5,12 +5,11 @@
defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
alias Pleroma.Config
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.MRF
require Pleroma.Constants
@moduledoc "Filter activities depending on their age"
- @behaviour MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF
defp check_date(%{"published" => published} = message) do
with %DateTime{} = now <- DateTime.utc_now(),
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 8e53296e7..b94c3c78a 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance"
- @behaviour MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF
require Pleroma.Constants
diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
index 566c1e191..77ffd1bb9 100644
--- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
require Logger
- @behaviour MRF
+ @behaviour Pleroma.Web.ActivityPub.MRF
defp lookup_subchain(actor) do
with matches <- Config.get([:mrf_subchain, :match_actor]),
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 2b8bfc3bd..a72d8430f 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -580,7 +580,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"star" => "⭐"
}
- @doc "Rewrite misskey likes into EmojiReactions"
+ @doc "Rewrite misskey likes into EmojiReacts"
def handle_incoming(
%{
"type" => "Like",
@@ -589,7 +589,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
options
) do
data
- |> Map.put("type", "EmojiReaction")
+ |> Map.put("type", "EmojiReact")
|> Map.put("content", @misskey_reactions[reaction] || reaction)
|> handle_incoming(options)
end
@@ -610,7 +610,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(
%{
- "type" => "EmojiReaction",
+ "type" => "EmojiReact",
"object" => object_id,
"actor" => _actor,
"id" => id,
@@ -751,7 +751,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(
%{
"type" => "Undo",
- "object" => %{"type" => "EmojiReaction", "id" => reaction_activity_id},
+ "object" => %{"type" => "EmojiReact", "id" => reaction_activity_id},
"actor" => _actor,
"id" => id
} = data,
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 4f7fdaf38..10ce5eee8 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -308,7 +308,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def make_emoji_reaction_data(user, object, emoji, activity_id) do
make_like_data(user, object, activity_id)
- |> Map.put("type", "EmojiReaction")
+ |> Map.put("type", "EmojiReact")
|> Map.put("content", emoji)
end
@@ -490,10 +490,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Repo.one()
end
+ def fetch_latest_undo(%User{ap_id: ap_id}) do
+ "Undo"
+ |> Activity.Queries.by_type()
+ |> where(actor: ^ap_id)
+ |> order_by([activity], fragment("? desc nulls last", activity.id))
+ |> limit(1)
+ |> Repo.one()
+ end
+
def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
%{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
- "EmojiReaction"
+ "EmojiReact"
|> Activity.Queries.by_type()
|> where(actor: ^ap_id)
|> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 2314d3274..67222ebae 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.ConfigDB
alias Pleroma.ModerationLog
alias Pleroma.Plugs.OAuthScopesPlug
@@ -97,7 +98,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
%{scopes: ["read"], admin: true}
- when action in [:config_show, :migrate_from_db, :list_log]
+ when action in [:config_show, :list_log]
)
plug(
@@ -570,8 +571,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@doc "Sends registration invite via email"
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
with true <-
- Pleroma.Config.get([:instance, :invites_enabled]) &&
- !Pleroma.Config.get([:instance, :registrations_open]),
+ Config.get([:instance, :invites_enabled]) &&
+ !Config.get([:instance, :registrations_open]),
{:ok, invite_token} <- UserInviteToken.create_invite(),
email <-
Pleroma.Emails.UserEmail.user_invitation_email(
@@ -793,33 +794,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> Plug.Conn.send_resp(200, @descriptions_json)
end
- def migrate_from_db(conn, _params) do
- with :ok <- configurable_from_database(conn) do
- Mix.Tasks.Pleroma.Config.run([
- "migrate_from_db",
- "--env",
- to_string(Pleroma.Config.get(:env)),
- "-d"
- ])
-
- json(conn, %{})
- end
- end
-
def config_show(conn, %{"only_db" => true}) do
with :ok <- configurable_from_database(conn) do
configs = Pleroma.Repo.all(ConfigDB)
- if configs == [] do
- errors(
- conn,
- {:error, "To use configuration from database migrate your settings to database."}
- )
- else
- conn
- |> put_view(ConfigView)
- |> render("index.json", %{configs: configs})
- end
+ conn
+ |> put_view(ConfigView)
+ |> render("index.json", %{configs: configs})
end
end
@@ -827,45 +808,47 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
with :ok <- configurable_from_database(conn) do
configs = ConfigDB.get_all_as_keyword()
- if configs == [] do
- errors(
- conn,
- {:error, "To use configuration from database migrate your settings to database."}
- )
- else
- merged =
- Pleroma.Config.Holder.config()
- |> ConfigDB.merge(configs)
- |> Enum.map(fn {group, values} ->
- Enum.map(values, fn {key, value} ->
- db =
- if configs[group][key] do
- ConfigDB.get_db_keys(configs[group][key], key)
- end
-
- db_value = configs[group][key]
-
- merged_value =
- if !is_nil(db_value) and Keyword.keyword?(db_value) and
- ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
- ConfigDB.merge_group(group, key, value, db_value)
- else
- value
- end
-
- setting = %{
- group: ConfigDB.convert(group),
- key: ConfigDB.convert(key),
- value: ConfigDB.convert(merged_value)
- }
-
- if db, do: Map.put(setting, :db, db), else: setting
- end)
+ merged =
+ Config.Holder.config()
+ |> ConfigDB.merge(configs)
+ |> Enum.map(fn {group, values} ->
+ Enum.map(values, fn {key, value} ->
+ db =
+ if configs[group][key] do
+ ConfigDB.get_db_keys(configs[group][key], key)
+ end
+
+ db_value = configs[group][key]
+
+ merged_value =
+ if !is_nil(db_value) and Keyword.keyword?(db_value) and
+ ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do
+ ConfigDB.merge_group(group, key, value, db_value)
+ else
+ value
+ end
+
+ setting = %{
+ group: ConfigDB.convert(group),
+ key: ConfigDB.convert(key),
+ value: ConfigDB.convert(merged_value)
+ }
+
+ if db, do: Map.put(setting, :db, db), else: setting
end)
- |> List.flatten()
+ end)
+ |> List.flatten()
- json(conn, %{configs: merged})
- end
+ response = %{configs: merged}
+
+ response =
+ if Restarter.Pleroma.need_reboot?() do
+ Map.put(response, :need_reboot, true)
+ else
+ response
+ end
+
+ json(conn, response)
end
end
@@ -890,22 +873,43 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
Ecto.get_meta(config, :state) == :deleted
end)
- Pleroma.Config.TransferTask.load_and_update_env(deleted)
+ Config.TransferTask.load_and_update_env(deleted, false)
+
+ need_reboot? =
+ Restarter.Pleroma.need_reboot?() ||
+ Enum.any?(updated, fn config ->
+ group = ConfigDB.from_string(config.group)
+ key = ConfigDB.from_string(config.key)
+ value = ConfigDB.from_binary(config.value)
+ Config.TransferTask.pleroma_need_restart?(group, key, value)
+ end)
+
+ response = %{configs: updated}
- Mix.Tasks.Pleroma.Config.run([
- "migrate_from_db",
- "--env",
- to_string(Pleroma.Config.get(:env))
- ])
+ response =
+ if need_reboot? do
+ Restarter.Pleroma.need_reboot()
+ Map.put(response, :need_reboot, need_reboot?)
+ else
+ response
+ end
conn
|> put_view(ConfigView)
- |> render("index.json", %{configs: updated})
+ |> render("index.json", response)
+ end
+ end
+
+ def restart(conn, _params) do
+ with :ok <- configurable_from_database(conn) do
+ Restarter.Pleroma.restart(Config.get(:env), 50)
+
+ json(conn, %{})
end
end
defp configurable_from_database(conn) do
- if Pleroma.Config.get(:configurable_from_database) do
+ if Config.get(:configurable_from_database) do
:ok
else
errors(
diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex
index 23d97e847..bbb53efcd 100644
--- a/lib/pleroma/web/admin_api/views/config_view.ex
+++ b/lib/pleroma/web/admin_api/views/config_view.ex
@@ -5,10 +5,16 @@
defmodule Pleroma.Web.AdminAPI.ConfigView do
use Pleroma.Web, :view
- def render("index.json", %{configs: configs}) do
- %{
+ def render("index.json", %{configs: configs} = params) do
+ map = %{
configs: render_many(configs, __MODULE__, "show.json", as: :config)
}
+
+ if params[:need_reboot] do
+ Map.put(map, :need_reboot, true)
+ else
+ map
+ end
end
def render("show.json", %{config: config}) do
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index c05a6c544..2a348dcf6 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -315,8 +315,9 @@ defmodule Pleroma.Web.CommonAPI do
with %Activity{
actor: ^user_ap_id,
data: %{"type" => "Create"},
- object: %Object{data: %{"type" => "Note"}}
+ object: %Object{data: %{"type" => object_type}}
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
+ true <- object_type in ["Note", "Article", "Question"],
true <- Visibility.is_public?(activity),
{:ok, _user} <- User.add_pinnned_activity(user, activity) do
{:ok, activity}
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index a9b164d9a..4445894b0 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -179,9 +179,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end)
end_time =
- NaiveDateTime.utc_now()
- |> NaiveDateTime.add(expires_in)
- |> NaiveDateTime.to_iso8601()
+ DateTime.utc_now()
+ |> DateTime.add(expires_in)
+ |> DateTime.to_iso8601()
key = if truthy_param?(data["poll"]["multiple"]), do: "anyOf", else: "oneOf"
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
@@ -228,9 +228,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
data,
visibility
) do
- no_attachment_links =
+ attachment_links =
data
- |> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links]))
+ |> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|> truthy_param?()
content_type = get_content_type(data["content_type"])
@@ -244,7 +244,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
status
|> format_input(content_type, options)
- |> maybe_add_attachments(attachments, no_attachment_links)
+ |> maybe_add_attachments(attachments, attachment_links)
|> maybe_add_nsfw_tag(data)
end
@@ -270,7 +270,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
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
+ def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed
def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do
text = add_attachments(text, attachments)
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index d32c38a05..a77b73109 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -61,7 +61,17 @@ defmodule Pleroma.Web.Endpoint do
plug(Plug.RequestId)
plug(Plug.Logger, log: :debug)
- plug(Pleroma.Plugs.Parsers)
+ plug(Plug.Parsers,
+ parsers: [
+ :urlencoded,
+ {:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}},
+ :json
+ ],
+ pass: ["*/*"],
+ json_decoder: Jason,
+ length: Pleroma.Config.get([:instance, :upload_limit]),
+ body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
+ )
plug(Plug.MethodOverride)
plug(Plug.Head)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 1149fb469..287d1631c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -124,15 +124,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
) do
params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
- if ScheduledActivity.far_enough?(scheduled_at) do
- with {:ok, scheduled_activity} <-
- ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
- conn
- |> put_view(ScheduledActivityView)
- |> render("show.json", scheduled_activity: scheduled_activity)
- end
+ with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)},
+ attrs <- %{"params" => params, "scheduled_at" => scheduled_at},
+ {:ok, scheduled_activity} <- ScheduledActivity.create(user, attrs) do
+ conn
+ |> put_view(ScheduledActivityView)
+ |> render("show.json", scheduled_activity: scheduled_activity)
else
- create(conn, Map.drop(params, ["scheduled_at"]))
+ {:far_enough, _} ->
+ create(conn, Map.drop(params, ["scheduled_at"]))
+
+ error ->
+ error
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index a5420f480..c6d37ead7 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -67,7 +67,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
defp do_render("show.json", %{user: user} = opts) do
- display_name = HTML.strip_tags(user.name || user.nickname)
+ display_name = user.name || user.nickname
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
@@ -105,7 +105,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
- "name" => Pleroma.HTML.strip_tags(name),
+ "name" => name,
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
index 753039da3..6bb3652fb 100644
--- a/lib/pleroma/web/mastodon_api/views/poll_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.MastodonAPI.PollView do
use Pleroma.Web, :view
- alias Pleroma.HTML
alias Pleroma.Web.CommonAPI.Utils
def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
@@ -57,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
current_count = option["replies"]["totalItems"] || 0
{%{
- title: HTML.strip_tags(name),
+ title: name,
votes_count: current_count
}, current_count + count}
end)
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index e60ef709b..6b0fe9215 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -175,9 +175,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
expires_at =
with true <- client_posted_this_activity,
- expiration when not is_nil(expiration) <-
+ %ActivityExpiration{scheduled_at: scheduled_at} <-
ActivityExpiration.get_by_activity_id(activity.id) do
- expiration.scheduled_at
+ scheduled_at
+ else
+ _ -> nil
end
thread_muted? =
@@ -216,21 +218,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
summary = object.data["summary"] || ""
- summary_html =
- summary
- |> HTML.get_cached_scrubbed_html_for_activity(
- User.html_filter_policy(opts[:for]),
- activity,
- "mastoapi:summary"
- )
-
- summary_plaintext =
- summary
- |> HTML.get_cached_stripped_html_for_activity(
- activity,
- "mastoapi:summary"
- )
-
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
url =
@@ -256,7 +243,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
emoji_reactions =
with %{data: %{"reactions" => emoji_reactions}} <- object do
Enum.map(emoji_reactions, fn [emoji, users] ->
- %{emoji: emoji, count: length(users)}
+ %{
+ name: emoji,
+ count: length(users),
+ me: !!(opts[:for] && opts[:for].ap_id in users)
+ }
end)
else
_ -> []
@@ -282,7 +273,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
muted: thread_muted? || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user),
sensitive: sensitive,
- spoiler_text: summary_html,
+ spoiler_text: summary,
visibility: get_visibility(object),
media_attachments: attachments,
poll: render(PollView, "show.json", object: object, for: opts[:for]),
@@ -299,7 +290,7 @@ 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},
expires_at: expires_at,
direct_conversation_id: direct_conversation_id,
thread_muted: thread_muted?,
@@ -332,11 +323,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
nil
end
- site_name = rich_media[:site_name] || page_url_data.host
-
%{
type: "link",
- provider_name: site_name,
+ provider_name: page_url_data.host,
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
image: image_url |> MediaProxy.url(),
diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/rel_me.ex
index f87fc1973..86dcc1a3b 100644
--- a/lib/pleroma/web/metadata/rel_me.ex
+++ b/lib/pleroma/web/metadata/rel_me.ex
@@ -8,8 +8,10 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do
@impl Provider
def build_tags(%{user: user}) do
- (Floki.attribute(user.bio, "link[rel~=me]", "href") ++
- Floki.attribute(user.bio, "a[rel~=me]", "href"))
+ bio_tree = Floki.parse_fragment!(user.bio)
+
+ (Floki.attribute(bio_tree, "link[rel~=me]", "href") ++
+ Floki.attribute(bio_tree, "a[rel~=me]", "href"))
|> Enum.map(fn link ->
{:link, [rel: "me", href: link], []}
end)
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index 03c35cc2a..333012920 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -46,10 +46,10 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
data
|> Map.merge(%{quarantined_instances: quarantined})
- |> Map.put(:enabled, Config.get([:instance, :federating]))
else
%{}
end
+ |> Map.put(:enabled, Config.get([:instance, :federating]))
features =
[
diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex
deleted file mode 100644
index 3c9c580d5..000000000
--- a/lib/pleroma/web/oauth/token/clean_worker.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-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
-
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Workers.BackgroundWorker
-
- def start_link(_), do: GenServer.start_link(__MODULE__, %{})
-
- def init(_) do
- Process.send_after(self(), :perform, @ten_seconds)
- {:ok, nil}
- end
-
- @doc false
- def handle_info(:perform, state) do
- BackgroundWorker.enqueue("clean_expired_tokens", %{})
- interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day)
-
- Process.send_after(self(), :perform, interval)
- {:noreply, state}
- end
-
- def perform(:clean), do: Token.delete_expired_tokens()
-end
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
index 0bbf84fd3..a2f6d2287 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
@@ -573,11 +573,14 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
assumed to be emojis and stored in the new `pack.json` file.
"""
def import_from_fs(conn, _params) do
- with {:ok, results} <- File.ls(emoji_dir_path()) do
+ emoji_path = emoji_dir_path()
+
+ with {:ok, %{access: :read_write}} <- File.stat(emoji_path),
+ {:ok, results} <- File.ls(emoji_path) do
imported_pack_names =
results
|> Enum.filter(fn file ->
- dir_path = Path.join(emoji_dir_path(), file)
+ dir_path = Path.join(emoji_path, file)
# Find the directories that do NOT have pack.json
File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json"))
end)
@@ -585,6 +588,11 @@ keeping it in cache for #{div(cache_ms, 1000)}s")
json(conn, imported_pack_names)
else
+ {:ok, %{access: _}} ->
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: "Error: emoji pack directory must be writable"})
+
{:error, _} ->
conn
|> put_status(:internal_server_error)
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index cd1c0764f..108e48438 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -47,13 +47,16 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
Object.normalize(activity) do
reactions =
emoji_reactions
- |> Enum.map(fn [emoji, users] ->
- users = Enum.map(users, &User.get_cached_by_ap_id/1)
+ |> Enum.map(fn [emoji, user_ap_ids] ->
+ users =
+ Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1)
+ |> Enum.filter(& &1)
%{
- emoji: emoji,
+ name: emoji,
count: length(users),
- accounts: AccountView.render("index.json", %{users: users, for: user, as: :user})
+ accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}),
+ me: !!(user && user.ap_id in user_ap_ids)
}
end)
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index 16b1a53d2..540fa65df 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -27,9 +27,10 @@ defmodule Pleroma.Web.RelMe do
defp parse_url(url) do
with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-
Pleroma.HTTP.get(url, [], adapter: @hackney_options),
+ {:ok, html_tree} <- Floki.parse_document(html),
data <-
- Floki.attribute(html, "link[rel~=me]", "href") ++
- Floki.attribute(html, "a[rel~=me]", "href") do
+ Floki.attribute(html_tree, "link[rel~=me]", "href") ++
+ Floki.attribute(html_tree, "a[rel~=me]", "href") do
{:ok, data}
end
rescue
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index c06b0a0f2..9702e90f1 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -81,18 +81,18 @@ defmodule Pleroma.Web.RichMedia.Parser do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
html
- |> parse_html
+ |> parse_html()
|> maybe_parse()
|> Map.put(:url, url)
|> clean_parsed_data()
|> check_parsed_data()
rescue
e ->
- {:error, "Parsing error: #{inspect(e)}"}
+ {:error, "Parsing error: #{inspect(e)} #{inspect(__STACKTRACE__)}"}
end
end
- defp parse_html(html), do: Floki.parse(html)
+ defp parse_html(html), do: Floki.parse_document!(html)
defp maybe_parse(html) do
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
index c2a3c07a2..fae3c462e 100644
--- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
@@ -48,6 +48,6 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
defp maybe_put_title(meta, _), do: meta
defp get_page_title(html) do
- Floki.find(html, "title") |> List.first() |> Floki.text()
+ Floki.find(html, "html head title") |> List.first() |> Floki.text()
end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index b5c1d85c7..897215698 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -196,7 +196,7 @@ defmodule Pleroma.Web.Router do
get("/config", AdminAPIController, :config_show)
post("/config", AdminAPIController, :config_update)
get("/config/descriptions", AdminAPIController, :config_descriptions)
- get("/config/migrate_from_db", AdminAPIController, :migrate_from_db)
+ get("/restart", AdminAPIController, :restart)
get("/moderation_log", AdminAPIController, :list_log)
@@ -271,7 +271,7 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:api)
- get("/statuses/:id/emoji_reactions_by", PleromaAPIController, :emoji_reactions_by)
+ get("/statuses/:id/reactions", PleromaAPIController, :emoji_reactions_by)
end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
@@ -287,8 +287,8 @@ defmodule Pleroma.Web.Router do
pipe_through(:authenticated_api)
patch("/conversations/:id", PleromaAPIController, :update_conversation)
- post("/statuses/:id/react_with_emoji", PleromaAPIController, :react_with_emoji)
- post("/statuses/:id/unreact_with_emoji", PleromaAPIController, :unreact_with_emoji)
+ put("/statuses/:id/reactions/:emoji", PleromaAPIController, :react_with_emoji)
+ delete("/statuses/:id/reactions/:emoji", PleromaAPIController, :unreact_with_emoji)
post("/notifications/read", PleromaAPIController, :read_notification)
patch("/accounts/update_avatar", AccountController, :update_avatar)
diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex
index a1b445f2f..5392c1ec3 100644
--- a/lib/pleroma/web/streamer/worker.ex
+++ b/lib/pleroma/web/streamer/worker.ex
@@ -138,7 +138,8 @@ defmodule Pleroma.Web.Streamer.Worker do
with parent <- Object.normalize(item) || item,
true <-
- Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)),
+ Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
+ true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor),
diff --git a/lib/pleroma/web/templates/email/new_users_digest.html.eex b/lib/pleroma/web/templates/email/new_users_digest.html.eex
new file mode 100644
index 000000000..40d9b8381
--- /dev/null
+++ b/lib/pleroma/web/templates/email/new_users_digest.html.eex
@@ -0,0 +1,158 @@
+<%= for {user, total_statuses, latest_status} <- @users_and_statuses do %>
+ <%# user card START %>
+ <div style="background-color:transparent;">
+ <div class="block-grid mixed-two-up no-stack"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num3"
+ style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;">
+ <!--<![endif]-->
+ <div align="left" class="img-container left "
+ style="padding-right: 0px;padding-left: 0px;">
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img
+ alt="<%= user.name %>" border="0" class="left " src="<%= avatar_url(user) %>"
+ style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;"
+ title="<%= user.name %>" width="76" />
+ <!--[if mso]></td></tr></table><![endif]-->
+ </div>
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num9"
+ style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
+ <p style="font-size: 14px; line-height: 19px; margin: 0;"><span
+ style="font-size: 16px; color: <%= @styling.text_color %>;"><%= user.name %></span></p>
+ <p style="font-size: 14px; line-height: 19px; margin: 0;"><span
+ style="font-size: 16px;"><%= link "@" <> user.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: admin_user_url(user) %></span></p>
+ <p style="font-size: 14px; line-height: 19px; margin: 0;"><span
+ style="font-size: 16px;">Total: <%= total_statuses %></span></p>
+ </div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <%# user card END %>
+
+ <%= if latest_status do %>
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;">
+ <!--<![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;">
+ <span style="font-size: 16px; line-height: 19px;"><%= raw latest_status.object.data["content"] %></span></div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 15px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="color:<%= @styling.text_muted_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:15px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_muted_color %>;">
+ <p style="font-size: 14px; line-height: 16px; margin: 0;"><%= format_date latest_status.object.data["published"] %></p>
+ </div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <% end %>
+ <%# divider start %>
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation"
+ style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td class="divider_inner"
+ style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;"
+ valign="top">
+ <table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content"
+ height="0" role="presentation"
+ style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td height="0"
+ style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"
+ valign="top"><span></span></td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+
+ <%# divider end %>
+ <%# user card END %>
+<% end %>
diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex
new file mode 100644
index 000000000..ca2caaf4d
--- /dev/null
+++ b/lib/pleroma/web/templates/layout/email_styled.html.eex
@@ -0,0 +1,193 @@
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"
+ xmlns:v="urn:schemas-microsoft-com:vml">
+
+<head>
+ <!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->
+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+ <meta content="width=device-width" name="viewport" />
+ <!--[if !mso]><!-->
+ <meta content="IE=edge" http-equiv="X-UA-Compatible" />
+ <!--<![endif]-->
+ <title><%= @email.subject %></title>
+ <!--[if !mso]><!-->
+ <!--<![endif]-->
+ <style type="text/css">
+ body {
+ margin: 0;
+ padding: 0;
+ }
+
+ a {
+
+ color: <%= @styling.link_color %>;
+ text-decoration: none;
+ }
+
+ table,
+ td,
+ tr {
+ vertical-align: top;
+ border-collapse: collapse;
+ }
+
+ * {
+ line-height: inherit;
+ }
+
+ a[x-apple-data-detectors=true] {
+ color: inherit !important;
+ text-decoration: none !important;
+ }
+ </style>
+ <style id="media-query" type="text/css">
+ @media (max-width: 610px) {
+
+ .block-grid,
+ .col {
+ min-width: 320px !important;
+ max-width: 100% !important;
+ display: block !important;
+ }
+
+ .block-grid {
+ width: 100% !important;
+ }
+
+ .col {
+ width: 100% !important;
+ }
+
+ .col>div {
+ margin: 0 auto;
+ }
+
+ .no-stack .col {
+ min-width: 0 !important;
+ display: table-cell !important;
+ }
+
+ .no-stack.two-up .col {
+ width: 50% !important;
+ }
+
+ .no-stack .col.num4 {
+ width: 33% !important;
+ }
+
+ .no-stack .col.num8 {
+ width: 66% !important;
+ }
+
+ .no-stack .col.num4 {
+ width: 33% !important;
+ }
+
+ .no-stack .col.num3 {
+ width: 25% !important;
+ }
+
+ .no-stack .col.num6 {
+ width: 50% !important;
+ }
+
+ .no-stack .col.num9 {
+ width: 75% !important;
+ }
+
+ }
+ </style>
+</head>
+
+<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: <%= @styling.background_color %>;">
+ <!--[if IE]><div class="ie-browser"><![endif]-->
+ <table bgcolor="<%= @styling.background_color %>" cellpadding="0" cellspacing="0" class="nl-container" role="presentation"
+ style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: <%= @styling.background_color %>; width: 100%;"
+ valign="top" width="100%">
+ <tbody>
+ <tr style="vertical-align: top;" valign="top">
+ <td style="word-break: break-word; vertical-align: top;" valign="top">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]-->
+
+ <%# header %>
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <div align="center" class="img-container center"
+ style="padding-right: 0px;padding-left: 0px;">
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img
+ align="center" alt="Image" border="0" class="center" src="<%= @logo_url %>"
+ style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;"
+ title="Image" height="80" />
+ <!--[if mso]></td></tr></table><![endif]-->
+ </div>
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+
+
+ <%# title %>
+ <%= if @title do %>
+ <div style="background-color:transparent;">
+ <div class="block-grid"
+ style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
+ <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;">
+ <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]-->
+ <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]-->
+ <div class="col num12"
+ style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;">
+ <div style="width:100% !important;">
+ <!--[if (!mso)&(!IE)]><!-->
+ <div
+ style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;">
+ <!--<![endif]-->
+ <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]-->
+ <div
+ style="line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;">
+ <div
+ style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;">
+ <p style="line-height: 36px; text-align: center; margin: 0;"><span
+ style="font-size: 30px; color: <%= @styling.header_color %>;"><%= @title %></span></p>
+ </div>
+ </div>
+ <!--[if mso]></td></tr></table><![endif]-->
+ <!--[if (!mso)&(!IE)]><!-->
+ </div>
+ <!--<![endif]-->
+ </div>
+ </div>
+ <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
+ <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]-->
+ </div>
+ </div>
+ </div>
+ <% end %>
+ <%= render @view_module, @view_template, assigns %>
+
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <!--[if (IE)]></div><![endif]-->
+</body>
+
+</html>
diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex
index b506a234b..6b0fbe61e 100644
--- a/lib/pleroma/web/views/email_view.ex
+++ b/lib/pleroma/web/views/email_view.ex
@@ -12,4 +12,8 @@ defmodule Pleroma.Web.EmailView do
|> Timex.parse!("{ISO:Extended:Z}")
|> Timex.format!("{Mshort} {D}, {YYYY} {h24}:{m}")
end
+
+ def admin_user_url(%{id: id}) do
+ Pleroma.Web.Endpoint.url() <> "/pleroma/admin/#/users/" <> id
+ end
end
diff --git a/lib/pleroma/workers/activity_expiration_worker.ex b/lib/pleroma/workers/activity_expiration_worker.ex
deleted file mode 100644
index 4e3e4195f..000000000
--- a/lib/pleroma/workers/activity_expiration_worker.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.ActivityExpirationWorker do
- use Pleroma.Workers.WorkerHelper, queue: "activity_expiration"
-
- @impl Oban.Worker
- def perform(
- %{
- "op" => "activity_expiration",
- "activity_expiration_id" => activity_expiration_id
- },
- _job
- ) do
- Pleroma.Daemons.ActivityExpirationDaemon.perform(:execute, activity_expiration_id)
- end
-end
diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
index 323a4da1e..ac2fe6946 100644
--- a/lib/pleroma/workers/background_worker.ex
+++ b/lib/pleroma/workers/background_worker.ex
@@ -6,7 +6,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy
- alias Pleroma.Web.OAuth.Token.CleanWorker
use Pleroma.Workers.WorkerHelper, queue: "background"
@@ -55,10 +54,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
User.perform(:follow_import, follower, followed_identifiers)
end
- def perform(%{"op" => "clean_expired_tokens"}, _job) do
- CleanWorker.perform(:clean)
- end
-
def perform(%{"op" => "media_proxy_preload", "message" => message}, _job) do
MediaProxyWarmingPolicy.perform(:preload, message)
end
diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
new file mode 100644
index 000000000..a24407874
--- /dev/null
+++ b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
+ @moduledoc """
+ The worker to cleanup expired oAuth tokens.
+ """
+
+ use Oban.Worker, queue: "background"
+
+ alias Pleroma.Config
+ alias Pleroma.Web.OAuth.Token
+
+ @impl Oban.Worker
+ def perform(_opts, _job) do
+ if Config.get([:oauth2, :clean_expired_tokens], false) do
+ Token.delete_expired_tokens()
+ end
+ end
+end
diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex
new file mode 100644
index 000000000..0a00129df
--- /dev/null
+++ b/lib/pleroma/workers/cron/digest_emails_worker.ex
@@ -0,0 +1,58 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
+ @moduledoc """
+ The worker to send digest emails.
+ """
+
+ use Oban.Worker, queue: "digest_emails"
+
+ alias Pleroma.Config
+ alias Pleroma.Emails
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ import Ecto.Query
+
+ require Logger
+
+ @impl Oban.Worker
+ def perform(_opts, _job) do
+ config = Config.get([:email_notifications, :digest])
+
+ if config[:active] do
+ negative_interval = -Map.fetch!(config, :interval)
+ inactivity_threshold = Map.fetch!(config, :inactivity_threshold)
+ inactive_users_query = User.list_inactive_users_query(inactivity_threshold)
+
+ now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
+
+ from(u in inactive_users_query,
+ where: fragment(~s(? ->'digest' @> 'true'), u.email_notifications),
+ where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
+ select: u
+ )
+ |> Repo.all()
+ |> send_emails
+ end
+ end
+
+ def send_emails(users) do
+ Enum.each(users, &send_email/1)
+ end
+
+ @doc """
+ Send digest email to the given user.
+ Updates `last_digest_emailed_at` field for the user and returns the updated user.
+ """
+ @spec send_email(User.t()) :: User.t()
+ def send_email(user) do
+ with %Swoosh.Email{} = email <- Emails.UserEmail.digest_email(user) do
+ Emails.Mailer.deliver_async(email)
+ end
+
+ User.touch_last_digest_emailed_at(user)
+ end
+end
diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex
new file mode 100644
index 000000000..951c2c054
--- /dev/null
+++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
+ alias Pleroma.Activity
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ import Ecto.Query
+
+ use Pleroma.Workers.WorkerHelper, queue: "new_users_digest"
+
+ @impl Oban.Worker
+ def perform(_args, _job) do
+ if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
+ today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()
+
+ a_day_ago =
+ today
+ |> Timex.shift(days: -1)
+ |> Timex.beginning_of_day()
+
+ users_and_statuses =
+ %{
+ local: true,
+ order_by: :inserted_at
+ }
+ |> User.Query.build()
+ |> where([u], u.inserted_at >= ^a_day_ago and u.inserted_at < ^today)
+ |> Repo.all()
+ |> Enum.map(fn user ->
+ latest_status =
+ Activity
+ |> Activity.Queries.by_actor(user.ap_id)
+ |> Activity.Queries.by_type("Create")
+ |> Activity.with_preloaded_object()
+ |> order_by(desc: :inserted_at)
+ |> limit(1)
+ |> Repo.one()
+
+ total_statuses =
+ Activity
+ |> Activity.Queries.by_actor(user.ap_id)
+ |> Activity.Queries.by_type("Create")
+ |> Repo.aggregate(:count, :id)
+
+ {user, total_statuses, latest_status}
+ end)
+
+ if users_and_statuses != [] do
+ %{is_admin: true}
+ |> User.Query.build()
+ |> Repo.all()
+ |> Enum.map(&Pleroma.Emails.NewUsersDigestEmail.new_users(&1, users_and_statuses))
+ |> Enum.each(&Pleroma.Emails.Mailer.deliver/1)
+ end
+ end
+ end
+end
diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
new file mode 100644
index 000000000..7a52860a9
--- /dev/null
+++ b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
+ @moduledoc """
+ The worker to purge expired activities.
+ """
+
+ use Oban.Worker, queue: "activity_expiration"
+
+ alias Pleroma.Activity
+ alias Pleroma.ActivityExpiration
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ require Logger
+
+ @interval :timer.minutes(1)
+
+ @impl Oban.Worker
+ def perform(_opts, _job) do
+ if Config.get([ActivityExpiration, :enabled]) do
+ Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
+ end
+ end
+
+ def delete_activity(%ActivityExpiration{activity_id: activity_id}) do
+ with {:activity, %Activity{} = activity} <-
+ {:activity, Activity.get_by_id_with_object(activity_id)},
+ {:user, %User{} = user} <- {:user, User.get_by_ap_id(activity.object.data["actor"])} do
+ CommonAPI.delete(activity.id, user)
+ else
+ {:activity, _} ->
+ Logger.error(
+ "#{__MODULE__} Couldn't delete expired activity: not found activity ##{activity_id}"
+ )
+
+ {:user, _} ->
+ Logger.error(
+ "#{__MODULE__} Couldn't delete expired activity: not found actorof ##{activity_id}"
+ )
+ end
+ end
+end
diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex
new file mode 100644
index 000000000..425ad41ca
--- /dev/null
+++ b/lib/pleroma/workers/cron/stats_worker.ex
@@ -0,0 +1,16 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.Cron.StatsWorker do
+ @moduledoc """
+ The worker to update peers statistics.
+ """
+
+ use Oban.Worker, queue: "background"
+
+ @impl Oban.Worker
+ def perform(_opts, _job) do
+ Pleroma.Stats.do_collect()
+ end
+end
diff --git a/lib/pleroma/workers/digest_emails_worker.ex b/lib/pleroma/workers/digest_emails_worker.ex
deleted file mode 100644
index 3e5a836d0..000000000
--- a/lib/pleroma/workers/digest_emails_worker.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.DigestEmailsWorker do
- alias Pleroma.User
-
- use Pleroma.Workers.WorkerHelper, queue: "digest_emails"
-
- @impl Oban.Worker
- def perform(%{"op" => "digest_email", "user_id" => user_id}, _job) do
- user_id
- |> User.get_cached_by_id()
- |> Pleroma.Daemons.DigestEmailDaemon.perform()
- end
-end
diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex
index ca7d53af1..bd41ab4ce 100644
--- a/lib/pleroma/workers/scheduled_activity_worker.ex
+++ b/lib/pleroma/workers/scheduled_activity_worker.ex
@@ -3,10 +3,42 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ScheduledActivityWorker do
+ @moduledoc """
+ The worker to post scheduled activity.
+ """
+
use Pleroma.Workers.WorkerHelper, queue: "scheduled_activities"
+ alias Pleroma.Config
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ require Logger
+
@impl Oban.Worker
- def perform(%{"op" => "execute", "activity_id" => activity_id}, _job) do
- Pleroma.Daemons.ScheduledActivityDaemon.perform(:execute, activity_id)
+ def perform(%{"activity_id" => activity_id}, _job) do
+ if Config.get([ScheduledActivity, :enabled]) do
+ case Pleroma.Repo.get(ScheduledActivity, activity_id) do
+ %ScheduledActivity{} = scheduled_activity ->
+ post_activity(scheduled_activity)
+
+ _ ->
+ Logger.error("#{__MODULE__} Couldn't find scheduled activity: #{activity_id}")
+ end
+ end
+ end
+
+ defp post_activity(%ScheduledActivity{user_id: user_id, params: params} = scheduled_activity) do
+ with {:delete, {:ok, _}} <- {:delete, ScheduledActivity.delete(scheduled_activity)},
+ {:user, %User{} = user} <- {:user, User.get_cached_by_id(user_id)},
+ {:post, {:ok, _}} <- {:post, CommonAPI.post(user, params)} do
+ :ok
+ else
+ error ->
+ Logger.error(
+ "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
+ )
+ end
end
end