@@ -64,6 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||||
- Support pagination in emoji packs API (for packs and for files in pack) | - Support pagination in emoji packs API (for packs and for files in pack) | ||||
- Support for viewing instances favicons next to posts and accounts | - Support for viewing instances favicons next to posts and accounts | ||||
- Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata. | - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata. | ||||
- Configuration: Add `:welcome` setting for welcoming message to a newly registered users. | |||||
<details> | <details> | ||||
<summary>API Changes</summary> | <summary>API Changes</summary> | ||||
@@ -225,8 +225,6 @@ config :pleroma, :instance, | |||||
autofollowed_nicknames: [], | autofollowed_nicknames: [], | ||||
max_pinned_statuses: 1, | max_pinned_statuses: 1, | ||||
attachment_links: false, | attachment_links: false, | ||||
welcome_user_nickname: nil, | |||||
welcome_message: nil, | |||||
max_report_comment_size: 1000, | max_report_comment_size: 1000, | ||||
safe_dm_mentions: false, | safe_dm_mentions: false, | ||||
healthcheck: false, | healthcheck: false, | ||||
@@ -254,6 +252,20 @@ config :pleroma, :instance, | |||||
] | ] | ||||
] | ] | ||||
config :pleroma, :welcome, | |||||
direct_message: [ | |||||
enabled: false, | |||||
sender_nickname: nil, | |||||
message: nil | |||||
], | |||||
email: [ | |||||
enabled: false, | |||||
sender_nickname: nil, | |||||
subject: "Welcome to <%= instance_name %>", | |||||
html: "Welcome to <%= instance_name %>", | |||||
text: "Welcome to <%= instance_name %>" | |||||
] | |||||
config :pleroma, :feed, | config :pleroma, :feed, | ||||
post_title: %{ | post_title: %{ | ||||
max_length: 100, | max_length: 100, | ||||
@@ -63,6 +63,18 @@ To add configuration to your config file, you can copy it from the base config. | |||||
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users. | * `external_user_synchronization`: Enabling following/followers counters synchronization for external users. | ||||
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances. | * `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances. | ||||
## Welcome | |||||
* `direct_message`: - welcome message sent as a direct message. | |||||
* `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`. | |||||
* `sender_nickname`: The nickname of the local user that sends the welcome message. | |||||
* `message`: A message that will be send to a newly registered users as a direct message. | |||||
* `email`: - welcome message sent as a email. | |||||
* `enabled`: Enables the send a welcome email to a newly registered user. Defaults to `false`. | |||||
* `sender_nickname`: The nickname of the local user that sends the welcome email. | |||||
* `subject`: A subject of welcome email. | |||||
* `html`: A html that will be send to a newly registered users as a email. | |||||
* `text`: A text that will be send to a newly registered users as a email. | |||||
## Message rewrite facility | ## Message rewrite facility | ||||
### :mrf | ### :mrf | ||||
@@ -0,0 +1,17 @@ | |||||
# Pleroma: A lightweight social networking server | |||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||||
# SPDX-License-Identifier: AGPL-3.0-only | |||||
defmodule Pleroma.Config.Helpers do | |||||
alias Pleroma.Config | |||||
def instance_name, do: Config.get([:instance, :name]) | |||||
defp instance_notify_email do | |||||
Config.get([:instance, :notify_email]) || Config.get([:instance, :email]) | |||||
end | |||||
def sender do | |||||
{instance_name(), instance_notify_email()} | |||||
end | |||||
end |
@@ -0,0 +1,17 @@ | |||||
# Pleroma: A lightweight social networking server | |||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||||
# SPDX-License-Identifier: AGPL-3.0-only | |||||
defmodule Pleroma.Config.Utils do | |||||
alias Pleroma.Config | |||||
def instance_name, do: Config.get([:instance, :name]) | |||||
defp instance_notify_email do | |||||
Config.get([:instance, :notify_email]) || Config.get([:instance, :email]) | |||||
end | |||||
def sender do | |||||
{instance_name(), instance_notify_email()} | |||||
end | |||||
end |
@@ -12,17 +12,22 @@ defmodule Pleroma.Emails.UserEmail do | |||||
alias Pleroma.Web.Endpoint | alias Pleroma.Web.Endpoint | ||||
alias Pleroma.Web.Router | alias Pleroma.Web.Router | ||||
defp instance_name, do: Config.get([:instance, :name]) | |||||
defp sender do | |||||
email = Config.get([:instance, :notify_email]) || Config.get([:instance, :email]) | |||||
{instance_name(), email} | |||||
end | |||||
import Pleroma.Config.Helpers, only: [instance_name: 0, sender: 0] | |||||
defp recipient(email, nil), do: email | defp recipient(email, nil), do: email | ||||
defp recipient(email, name), do: {name, email} | defp recipient(email, name), do: {name, email} | ||||
defp recipient(%User{} = user), do: recipient(user.email, user.name) | defp recipient(%User{} = user), do: recipient(user.email, user.name) | ||||
@spec welcome(User.t(), map()) :: Swoosh.Email.t() | |||||
def welcome(user, opts \\ %{}) do | |||||
new() | |||||
|> to(recipient(user)) | |||||
|> from(Map.get(opts, :sender, sender())) | |||||
|> subject(Map.get(opts, :subject, "Welcome to #{instance_name()}!")) | |||||
|> html_body(Map.get(opts, :html, "Welcome to #{instance_name()}!")) | |||||
|> text_body(Map.get(opts, :text, "Welcome to #{instance_name()}!")) | |||||
end | |||||
def password_reset_email(user, token) when is_binary(token) do | def password_reset_email(user, token) when is_binary(token) do | ||||
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token) | password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token) | ||||
@@ -713,12 +713,31 @@ defmodule Pleroma.User do | |||||
def post_register_action(%User{} = user) do | def post_register_action(%User{} = user) do | ||||
with {:ok, user} <- autofollow_users(user), | with {:ok, user} <- autofollow_users(user), | ||||
{:ok, user} <- set_cache(user), | {:ok, user} <- set_cache(user), | ||||
{:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user), | |||||
{:ok, _} <- send_welcome_email(user), | |||||
{:ok, _} <- send_welcome_message(user), | |||||
{:ok, _} <- try_send_confirmation_email(user) do | {:ok, _} <- try_send_confirmation_email(user) do | ||||
{:ok, user} | {:ok, user} | ||||
end | end | ||||
end | end | ||||
def send_welcome_message(user) do | |||||
if User.WelcomeMessage.enabled?() do | |||||
User.WelcomeMessage.post_message(user) | |||||
{:ok, :enqueued} | |||||
else | |||||
{:ok, :noop} | |||||
end | |||||
end | |||||
def send_welcome_email(user) do | |||||
if User.WelcomeEmail.enabled?() do | |||||
User.WelcomeEmail.send_email(user) | |||||
{:ok, :enqueued} | |||||
else | |||||
{:ok, :noop} | |||||
end | |||||
end | |||||
def try_send_confirmation_email(%User{} = user) do | def try_send_confirmation_email(%User{} = user) do | ||||
if user.confirmation_pending && | if user.confirmation_pending && | ||||
Config.get([:instance, :account_activation_required]) do | Config.get([:instance, :account_activation_required]) do | ||||
@@ -0,0 +1,68 @@ | |||||
# Pleroma: A lightweight social networking server | |||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> | |||||
# SPDX-License-Identifier: AGPL-3.0-only | |||||
defmodule Pleroma.User.WelcomeEmail do | |||||
@moduledoc """ | |||||
The module represents the functions to send welcome email. | |||||
""" | |||||
alias Pleroma.Config | |||||
alias Pleroma.Emails | |||||
alias Pleroma.User | |||||
import Pleroma.Config.Utils, only: [instance_name: 0] | |||||
@spec enabled?() :: boolean() | |||||
def enabled?, do: Config.get([:welcome, :email, :enabled], false) | |||||
@spec send_email(User.t()) :: {:ok, Oban.Job.t()} | |||||
def send_email(%User{} = user) do | |||||
user | |||||
|> Emails.UserEmail.welcome(email_options(user)) | |||||
|> Emails.Mailer.deliver_async() | |||||
end | |||||
defp email_options(user) do | |||||
bindings = [user: user, instance_name: instance_name()] | |||||
%{} | |||||
|> add_sender(Config.get([:welcome, :email, :sender_nickname], nil)) | |||||
|> add_option(:subject, bindings) | |||||
|> add_option(:html, bindings) | |||||
|> add_option(:text, bindings) | |||||
end | |||||
defp add_option(opts, option, bindings) do | |||||
[:welcome, :email, option] | |||||
|> Config.get(nil) | |||||
|> eval_string(bindings) | |||||
|> merge_options(opts, option) | |||||
end | |||||
def add_sender(opts, nickname) do | |||||
nickname | |||||
|> fetch_sender() | |||||
|> merge_options(opts, :sender) | |||||
end | |||||
defp merge_options(nil, options, _option), do: options | |||||
defp merge_options(value, options, option) do | |||||
Map.merge(options, %{option => value}) | |||||
end | |||||
defp fetch_sender(nickname) when is_binary(nickname) do | |||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do | |||||
{instance_name(), user.email} | |||||
else | |||||
_ -> nil | |||||
end | |||||
end | |||||
defp fetch_sender(_), do: nil | |||||
defp eval_string(nil, _), do: nil | |||||
defp eval_string("", _), do: nil | |||||
defp eval_string(str, bindings), do: EEx.eval_string(str, bindings) | |||||
end |
@@ -3,32 +3,45 @@ | |||||
# SPDX-License-Identifier: AGPL-3.0-only | # SPDX-License-Identifier: AGPL-3.0-only | ||||
defmodule Pleroma.User.WelcomeMessage do | defmodule Pleroma.User.WelcomeMessage do | ||||
alias Pleroma.Config | |||||
alias Pleroma.User | alias Pleroma.User | ||||
alias Pleroma.Web.CommonAPI | alias Pleroma.Web.CommonAPI | ||||
def post_welcome_message_to_user(user) do | |||||
with %User{} = sender_user <- welcome_user(), | |||||
message when is_binary(message) <- welcome_message() do | |||||
CommonAPI.post(sender_user, %{ | |||||
@spec enabled?() :: boolean() | |||||
def enabled?, do: Config.get([:welcome, :direct_message, :enabled], false) | |||||
@spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil} | |||||
def post_message(user) do | |||||
[:welcome, :direct_message, :sender_nickname] | |||||
|> Config.get(nil) | |||||
|> fetch_sender() | |||||
|> do_post(user, welcome_message()) | |||||
end | |||||
defp do_post(%User{} = sender, %User{nickname: nickname}, message) | |||||
when is_binary(message) do | |||||
CommonAPI.post( | |||||
sender, | |||||
%{ | |||||
visibility: "direct", | visibility: "direct", | ||||
status: "@#{user.nickname}\n#{message}" | |||||
}) | |||||
else | |||||
_ -> {:ok, nil} | |||||
end | |||||
status: "@#{nickname}\n#{message}" | |||||
} | |||||
) | |||||
end | end | ||||
defp welcome_user do | |||||
with nickname when is_binary(nickname) <- | |||||
Pleroma.Config.get([:instance, :welcome_user_nickname]), | |||||
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do | |||||
defp do_post(_sender, _recipient, _message), do: {:ok, nil} | |||||
defp fetch_sender(nickname) when is_binary(nickname) do | |||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do | |||||
user | user | ||||
else | else | ||||
_ -> nil | _ -> nil | ||||
end | end | ||||
end | end | ||||
defp fetch_sender(_), do: nil | |||||
defp welcome_message do | defp welcome_message do | ||||
Pleroma.Config.get([:instance, :welcome_message]) | |||||
Config.get([:welcome, :direct_message, :message], nil) | |||||
end | end | ||||
end | end |
@@ -10,6 +10,7 @@ defmodule Pleroma.Emails.UserEmailTest do | |||||
alias Pleroma.Web.Router | alias Pleroma.Web.Router | ||||
import Pleroma.Factory | import Pleroma.Factory | ||||
import Swoosh.TestAssertions | |||||
test "build password reset email" do | test "build password reset email" do | ||||
config = Pleroma.Config.get(:instance) | config = Pleroma.Config.get(:instance) | ||||
@@ -129,8 +129,6 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do | |||||
autofollowed_nicknames: [], | autofollowed_nicknames: [], | ||||
max_pinned_statuses: 1, | max_pinned_statuses: 1, | ||||
attachment_links: false, | attachment_links: false, | ||||
welcome_user_nickname: nil, | |||||
welcome_message: nil, | |||||
max_report_comment_size: 1000, | max_report_comment_size: 1000, | ||||
safe_dm_mentions: false, | safe_dm_mentions: false, | ||||
healthcheck: false, | healthcheck: false, | ||||
@@ -172,7 +170,7 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do | |||||
end | end | ||||
assert file == | assert file == | ||||
"#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n welcome_user_nickname: nil,\n welcome_message: nil,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n" | |||||
"#{header}\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n chat_limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n" | |||||
end | end | ||||
end | end | ||||
end | end |
@@ -17,6 +17,7 @@ defmodule Pleroma.UserTest do | |||||
import Pleroma.Factory | import Pleroma.Factory | ||||
import ExUnit.CaptureLog | import ExUnit.CaptureLog | ||||
import Swoosh.TestAssertions | |||||
setup_all do | setup_all do | ||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) | Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) | ||||
@@ -386,8 +387,8 @@ defmodule Pleroma.UserTest do | |||||
email: "email@example.com" | email: "email@example.com" | ||||
} | } | ||||
setup do: clear_config([:instance, :autofollowed_nicknames]) | setup do: clear_config([:instance, :autofollowed_nicknames]) | ||||
setup do: clear_config([:instance, :welcome_message]) | |||||
setup do: clear_config([:instance, :welcome_user_nickname]) | |||||
setup do: clear_config([:welcome]) | |||||
test "it autofollows accounts that are set for it" do | test "it autofollows accounts that are set for it" do | ||||
user = insert(:user) | user = insert(:user) | ||||
@@ -408,17 +409,35 @@ defmodule Pleroma.UserTest do | |||||
test "it sends a welcome message if it is set" do | test "it sends a welcome message if it is set" do | ||||
welcome_user = insert(:user) | welcome_user = insert(:user) | ||||
Pleroma.Config.put([:welcome, :direct_message, :enabled], true) | |||||
Pleroma.Config.put([:welcome, :direct_message, :sender_nickname], welcome_user.nickname) | |||||
Pleroma.Config.put([:welcome, :direct_message, :message], "Hello, this is a cool site") | |||||
Pleroma.Config.put([:welcome, :email, :enabled], true) | |||||
Pleroma.Config.put([:welcome, :email, :sender_nickname], welcome_user.nickname) | |||||
Pleroma.Config.put( | |||||
[:welcome, :email, :subject], | |||||
"Hello, welcome to cool site: <%= instance_name %>" | |||||
) | |||||
Pleroma.Config.put([:instance, :welcome_user_nickname], welcome_user.nickname) | |||||
Pleroma.Config.put([:instance, :welcome_message], "Hello, this is a cool site") | |||||
instance_name = Pleroma.Config.get([:instance, :name]) | |||||
cng = User.register_changeset(%User{}, @full_user_data) | cng = User.register_changeset(%User{}, @full_user_data) | ||||
{:ok, registered_user} = User.register(cng) | {:ok, registered_user} = User.register(cng) | ||||
ObanHelpers.perform_all() | |||||
activity = Repo.one(Pleroma.Activity) | activity = Repo.one(Pleroma.Activity) | ||||
assert registered_user.ap_id in activity.recipients | assert registered_user.ap_id in activity.recipients | ||||
assert Object.normalize(activity).data["content"] =~ "cool site" | assert Object.normalize(activity).data["content"] =~ "cool site" | ||||
assert activity.actor == welcome_user.ap_id | assert activity.actor == welcome_user.ap_id | ||||
assert_email_sent( | |||||
from: {instance_name, welcome_user.email}, | |||||
to: {registered_user.name, registered_user.email}, | |||||
subject: "Hello, welcome to cool site: #{instance_name}", | |||||
html_body: "Welcome to #{instance_name}" | |||||
) | |||||
end | end | ||||
setup do: clear_config([:instance, :account_activation_required]) | setup do: clear_config([:instance, :account_activation_required]) | ||||