From 9eea80002673eb1359a2d4369c65a89c42c2f707 Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Thu, 28 May 2020 10:16:09 -0500
Subject: [PATCH 001/213] Refactor notification settings
---
CHANGELOG.md | 3 ++
docs/API/pleroma_api.md | 7 ++---
lib/pleroma/notification.ex | 29 ++++++-------------
lib/pleroma/user/notification_setting.ex | 14 ++++-----
lib/pleroma/web/api_spec/schemas/account.ex | 14 ++++-----
test/notification_test.exs | 17 +++--------
.../mastodon_api/views/account_view_test.exs | 7 ++---
test/web/twitter_api/util_controller_test.exs | 16 +++++-----
8 files changed, 41 insertions(+), 66 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dabc2a85a..fba236608 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
API Changes
- **Breaking:** Emoji API: changed methods and renamed routes.
+- **Breaking:** Notification Settings API for suppressing notification
+ now supports the following controls: `from_followers`, `from_following`,
+ and `from_strangers`.
### Removed
diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
index 70d4755b7..2cb0792db 100644
--- a/docs/API/pleroma_api.md
+++ b/docs/API/pleroma_api.md
@@ -287,10 +287,9 @@ See [Admin-API](admin_api.md)
* Method `PUT`
* Authentication: required
* Params:
- * `followers`: BOOLEAN field, receives notifications from followers
- * `follows`: BOOLEAN field, receives notifications from people the user follows
- * `remote`: BOOLEAN field, receives notifications from people on remote instances
- * `local`: BOOLEAN field, receives notifications from people on the local instance
+ * `from_followers`: BOOLEAN field, receives notifications from followers
+ * `from_following`: BOOLEAN field, receives notifications from people the user follows
+ * `from_strangers`: BOOLEAN field, receives notifications from people without an established relationship
* `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 7eca55ac9..ca556f0bb 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -459,10 +459,9 @@ defmodule Pleroma.Notification do
def skip?(%Activity{} = activity, %User{} = user) do
[
:self,
- :followers,
- :follows,
- :non_followers,
- :non_follows,
+ :from_followers,
+ :from_following,
+ :from_strangers,
:recently_followed
]
|> Enum.find(&skip?(&1, activity, user))
@@ -476,9 +475,9 @@ defmodule Pleroma.Notification do
end
def skip?(
- :followers,
+ :from_followers,
%Activity{} = activity,
- %User{notification_settings: %{followers: false}} = user
+ %User{notification_settings: %{from_followers: false}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
@@ -486,9 +485,9 @@ defmodule Pleroma.Notification do
end
def skip?(
- :non_followers,
+ :from_strangers,
%Activity{} = activity,
- %User{notification_settings: %{non_followers: false}} = user
+ %User{notification_settings: %{from_strangers: false}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
@@ -496,25 +495,15 @@ defmodule Pleroma.Notification do
end
def skip?(
- :follows,
+ :from_following,
%Activity{} = activity,
- %User{notification_settings: %{follows: false}} = user
+ %User{notification_settings: %{from_following: false}} = user
) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
User.following?(user, followed)
end
- def skip?(
- :non_follows,
- %Activity{} = activity,
- %User{notification_settings: %{non_follows: false}} = user
- ) do
- actor = activity.data["actor"]
- followed = User.get_cached_by_ap_id(actor)
- !User.following?(user, followed)
- end
-
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
actor = activity.data["actor"]
diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex
index 4bd55e139..e47ac4cab 100644
--- a/lib/pleroma/user/notification_setting.ex
+++ b/lib/pleroma/user/notification_setting.ex
@@ -10,20 +10,18 @@ defmodule Pleroma.User.NotificationSetting do
@primary_key false
embedded_schema do
- field(:followers, :boolean, default: true)
- field(:follows, :boolean, default: true)
- field(:non_follows, :boolean, default: true)
- field(:non_followers, :boolean, default: true)
+ field(:from_followers, :boolean, default: true)
+ field(:from_following, :boolean, default: true)
+ field(:from_strangers, :boolean, default: true)
field(:privacy_option, :boolean, default: false)
end
def changeset(schema, params) do
schema
|> cast(prepare_attrs(params), [
- :followers,
- :follows,
- :non_follows,
- :non_followers,
+ :from_followers,
+ :from_following,
+ :from_strangers,
:privacy_option
])
end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index d54e2158d..ed90ef3db 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -57,10 +57,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
notification_settings: %Schema{
type: :object,
properties: %{
- followers: %Schema{type: :boolean},
- follows: %Schema{type: :boolean},
- non_followers: %Schema{type: :boolean},
- non_follows: %Schema{type: :boolean},
+ from_followers: %Schema{type: :boolean},
+ from_following: %Schema{type: :boolean},
+ from_strangers: %Schema{type: :boolean},
privacy_option: %Schema{type: :boolean}
}
},
@@ -123,10 +122,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"unread_conversation_count" => 0,
"tags" => [],
"notification_settings" => %{
- "followers" => true,
- "follows" => true,
- "non_followers" => true,
- "non_follows" => true,
+ "from_followers" => true,
+ "from_following" => true,
+ "from_strangers" => true,
"privacy_option" => false
},
"relationship" => %{
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 37c255fee..fd59aceb5 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -237,19 +237,19 @@ defmodule Pleroma.NotificationTest do
follower = insert(:user)
followed =
- insert(:user, notification_settings: %Pleroma.User.NotificationSetting{followers: false})
+ insert(:user, notification_settings: %Pleroma.User.NotificationSetting{from_followers: false})
User.follow(follower, followed)
{:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
refute Notification.create_notification(activity, followed)
end
- test "it disables notifications from non-followers" do
+ test "it disables notifications from strangers" do
follower = insert(:user)
followed =
insert(:user,
- notification_settings: %Pleroma.User.NotificationSetting{non_followers: false}
+ notification_settings: %Pleroma.User.NotificationSetting{from_strangers: false}
)
{:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
@@ -258,7 +258,7 @@ defmodule Pleroma.NotificationTest do
test "it disables notifications from people the user follows" do
follower =
- insert(:user, notification_settings: %Pleroma.User.NotificationSetting{follows: false})
+ insert(:user, notification_settings: %Pleroma.User.NotificationSetting{from_following: false})
followed = insert(:user)
User.follow(follower, followed)
@@ -267,15 +267,6 @@ defmodule Pleroma.NotificationTest do
refute Notification.create_notification(activity, follower)
end
- test "it disables notifications from people the user does not follow" do
- follower =
- insert(:user, notification_settings: %Pleroma.User.NotificationSetting{non_follows: false})
-
- followed = insert(:user)
- {:ok, activity} = CommonAPI.post(followed, %{status: "hey @#{follower.nickname}"})
- refute Notification.create_notification(activity, follower)
- end
-
test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 487ec26c2..2e01689ff 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -94,10 +94,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
user = insert(:user)
notification_settings = %{
- followers: true,
- follows: true,
- non_followers: true,
- non_follows: true,
+ from_followers: true,
+ from_following: true,
+ from_strangers: true,
privacy_option: false
}
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index ad919d341..1133107f4 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -191,7 +191,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
test "it updates notification settings", %{user: user, conn: conn} do
conn
|> put("/api/pleroma/notification_settings", %{
- "followers" => false,
+ "from_followers" => false,
"bar" => 1
})
|> json_response(:ok)
@@ -199,10 +199,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
user = refresh_record(user)
assert %Pleroma.User.NotificationSetting{
- followers: false,
- follows: true,
- non_follows: true,
- non_followers: true,
+ from_followers: false,
+ from_following: true,
+ from_strangers: true,
privacy_option: false
} == user.notification_settings
end
@@ -215,10 +214,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
user = refresh_record(user)
assert %Pleroma.User.NotificationSetting{
- followers: true,
- follows: true,
- non_follows: true,
- non_followers: true,
+ from_followers: true,
+ from_following: true,
+ from_strangers: true,
privacy_option: true
} == user.notification_settings
end
From 4c82f657c5aaba6aacbc8ef927c1727509195839 Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Thu, 28 May 2020 13:22:28 -0500
Subject: [PATCH 002/213] Formatting
---
test/notification_test.exs | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/test/notification_test.exs b/test/notification_test.exs
index fd59aceb5..a1a7cee2a 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -237,7 +237,9 @@ defmodule Pleroma.NotificationTest do
follower = insert(:user)
followed =
- insert(:user, notification_settings: %Pleroma.User.NotificationSetting{from_followers: false})
+ insert(:user,
+ notification_settings: %Pleroma.User.NotificationSetting{from_followers: false}
+ )
User.follow(follower, followed)
{:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
@@ -258,7 +260,9 @@ defmodule Pleroma.NotificationTest do
test "it disables notifications from people the user follows" do
follower =
- insert(:user, notification_settings: %Pleroma.User.NotificationSetting{from_following: false})
+ insert(:user,
+ notification_settings: %Pleroma.User.NotificationSetting{from_following: false}
+ )
followed = insert(:user)
User.follow(follower, followed)
From d4b20c96c4030ebb5eb908dc6efcf45be7a8355d Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Thu, 28 May 2020 15:34:11 -0500
Subject: [PATCH 003/213] Migrate old notification settings to new variants
---
...439_users_update_notification_settings.exs | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 priv/repo/migrations/20200528160439_users_update_notification_settings.exs
diff --git a/priv/repo/migrations/20200528160439_users_update_notification_settings.exs b/priv/repo/migrations/20200528160439_users_update_notification_settings.exs
new file mode 100644
index 000000000..561f7a2c4
--- /dev/null
+++ b/priv/repo/migrations/20200528160439_users_update_notification_settings.exs
@@ -0,0 +1,43 @@
+defmodule Pleroma.Repo.Migrations.UsersUpdateNotificationSettings do
+ use Ecto.Migration
+
+ def up do
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'followers' || jsonb_build_object('from_followers', notification_settings->'followers')
+where notification_settings ? 'followers'
+and local"
+ )
+
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'follows' || jsonb_build_object('from_following', notification_settings->'follows')
+where notification_settings ? 'follows'
+and local"
+ )
+
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'non_followers' || jsonb_build_object('from_strangers', notification_settings->'non_followers')
+where notification_settings ? 'non_followers'
+and local"
+ )
+ end
+
+ def down do
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'from_followers' || jsonb_build_object('followers', notification_settings->'from_followers')
+where notification_settings ? 'from_followers'
+and local"
+ )
+
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'from_following' || jsonb_build_object('follows', notification_settings->'from_following')
+where notification_settings ? 'from_following'
+and local"
+ )
+
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'from_strangers' || jsonb_build_object('non_follows', notification_settings->'from_strangers')
+where notification_settings ? 'from_strangers'
+and local"
+ )
+ end
+end
From a8d967762ec5436ca9b478fbbedfec39b5d9e35e Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Tue, 23 Jun 2020 15:09:01 +0300
Subject: [PATCH 004/213] migrate to oban 2.0-rc1
---
config/config.exs | 3 +-
config/test.exs | 4 +--
lib/pleroma/application.ex | 14 +++++++-
.../workers/attachments_cleanup_worker.ex | 11 +++---
lib/pleroma/workers/background_worker.ex | 34 +++++++++----------
.../workers/cron/clear_oauth_token_worker.ex | 2 +-
.../workers/cron/digest_emails_worker.ex | 2 +-
.../workers/cron/new_users_digest_worker.ex | 2 +-
.../cron/purge_expired_activities_worker.ex | 2 +-
lib/pleroma/workers/cron/stats_worker.ex | 2 +-
lib/pleroma/workers/mailer_worker.ex | 2 +-
lib/pleroma/workers/publisher_worker.ex | 6 ++--
lib/pleroma/workers/receiver_worker.ex | 2 +-
lib/pleroma/workers/remote_fetcher_worker.ex | 8 +----
.../workers/scheduled_activity_worker.ex | 2 +-
lib/pleroma/workers/transmogrifier_worker.ex | 2 +-
lib/pleroma/workers/web_pusher_worker.ex | 2 +-
lib/pleroma/workers/worker_helper.ex | 4 ++-
mix.exs | 2 +-
mix.lock | 8 ++---
test/activity_expiration_test.exs | 2 +-
test/support/oban_helpers.ex | 2 +-
test/web/activity_pub/activity_pub_test.exs | 2 +-
.../cron/clear_oauth_token_worker_test.exs | 2 +-
.../cron/digest_emails_worker_test.exs | 4 +--
.../cron/new_users_digest_worker_test.exs | 4 +--
.../purge_expired_activities_worker_test.exs | 4 +--
.../scheduled_activity_worker_test.exs | 7 ++--
28 files changed, 72 insertions(+), 69 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index e0888fa9a..dcf4291d6 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -494,8 +494,7 @@ config :pleroma, Pleroma.User,
config :pleroma, Oban,
repo: Pleroma.Repo,
- verbose: false,
- prune: {:maxlen, 1500},
+ log: false,
queues: [
activity_expiration: 10,
federator_incoming: 50,
diff --git a/config/test.exs b/config/test.exs
index e38b9967d..054fac355 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -79,8 +79,8 @@ config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock
config :pleroma, Oban,
queues: false,
- prune: :disabled,
- crontab: false
+ crontab: false,
+ plugins: false
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 2,
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 9615af122..fb2731f97 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -80,7 +80,7 @@ defmodule Pleroma.Application do
[
Pleroma.Stats,
Pleroma.JobQueueMonitor,
- {Oban, Config.get(Oban)}
+ {Oban, oban_config()}
] ++
task_children(@env) ++
streamer_child(@env) ++
@@ -138,6 +138,18 @@ defmodule Pleroma.Application do
Pleroma.Web.Endpoint.Instrumenter.setup()
end
+ defp oban_config do
+ config = Config.get(Oban)
+
+ if Code.ensure_loaded?(IEx) and IEx.started?() do
+ config
+ |> Keyword.put(:crontab, false)
+ |> Keyword.put(:queues, false)
+ else
+ config
+ end
+ end
+
defp cachex_children do
[
build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex
index 8deeabda0..58226b395 100644
--- a/lib/pleroma/workers/attachments_cleanup_worker.ex
+++ b/lib/pleroma/workers/attachments_cleanup_worker.ex
@@ -11,13 +11,12 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
@impl Oban.Worker
- def perform(
- %{
+ def perform(%Job{
+ args: %{
"op" => "cleanup_attachments",
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
- },
- _job
- ) do
+ }
+ }) do
attachments
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
|> fetch_objects
@@ -28,7 +27,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
{:ok, :success}
end
- def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip}
+ def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
defp do_clean({object_ids, attachment_urls}) do
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
index 57c3a9c3a..cec5a7462 100644
--- a/lib/pleroma/workers/background_worker.ex
+++ b/lib/pleroma/workers/background_worker.ex
@@ -11,59 +11,59 @@ defmodule Pleroma.Workers.BackgroundWorker do
@impl Oban.Worker
- def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do
+ def perform(%Job{args: %{"op" => "deactivate_user", "user_id" => user_id, "status" => status}}) do
user = User.get_cached_by_id(user_id)
User.perform(:deactivate_async, user, status)
end
- def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do
+ def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do
user = User.get_cached_by_id(user_id)
User.perform(:delete, user)
end
- def perform(%{"op" => "force_password_reset", "user_id" => user_id}, _job) do
+ def perform(%Job{args: %{"op" => "force_password_reset", "user_id" => user_id}}) do
user = User.get_cached_by_id(user_id)
User.perform(:force_password_reset, user)
end
- def perform(
- %{
+ def perform(%Job{
+ args: %{
"op" => "blocks_import",
"blocker_id" => blocker_id,
"blocked_identifiers" => blocked_identifiers
- },
- _job
- ) do
+ }
+ }) do
blocker = User.get_cached_by_id(blocker_id)
{:ok, User.perform(:blocks_import, blocker, blocked_identifiers)}
end
- def perform(
- %{
+ def perform(%Job{
+ args: %{
"op" => "follow_import",
"follower_id" => follower_id,
"followed_identifiers" => followed_identifiers
- },
- _job
- ) do
+ }
+ }) do
follower = User.get_cached_by_id(follower_id)
{:ok, User.perform(:follow_import, follower, followed_identifiers)}
end
- def perform(%{"op" => "media_proxy_preload", "message" => message}, _job) do
+ def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do
MediaProxyWarmingPolicy.perform(:preload, message)
end
- def perform(%{"op" => "media_proxy_prefetch", "url" => url}, _job) do
+ def perform(%Job{args: %{"op" => "media_proxy_prefetch", "url" => url}}) do
MediaProxyWarmingPolicy.perform(:prefetch, url)
end
- def perform(%{"op" => "fetch_data_for_activity", "activity_id" => activity_id}, _job) do
+ def perform(%Job{args: %{"op" => "fetch_data_for_activity", "activity_id" => activity_id}}) do
activity = Activity.get_by_id(activity_id)
Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
end
- def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do
+ def perform(%Job{
+ args: %{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}
+ }) do
origin = User.get_cached_by_id(origin_id)
target = User.get_cached_by_id(target_id)
diff --git a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
index a4c3b9516..d41be4e87 100644
--- a/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
+++ b/lib/pleroma/workers/cron/clear_oauth_token_worker.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
alias Pleroma.Web.OAuth.Token
@impl Oban.Worker
- def perform(_opts, _job) do
+ def perform(_job) do
if Config.get([:oauth2, :clean_expired_tokens], false) do
Token.delete_expired_tokens()
else
diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex
index 7f09ff3cf..ee646229f 100644
--- a/lib/pleroma/workers/cron/digest_emails_worker.ex
+++ b/lib/pleroma/workers/cron/digest_emails_worker.ex
@@ -19,7 +19,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
require Logger
@impl Oban.Worker
- def perform(_opts, _job) do
+ def perform(_job) do
config = Config.get([:email_notifications, :digest])
if config[:active] do
diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex
index 5c816b3fe..abc8a5e95 100644
--- a/lib/pleroma/workers/cron/new_users_digest_worker.ex
+++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
use Pleroma.Workers.WorkerHelper, queue: "new_users_digest"
@impl Oban.Worker
- def perform(_args, _job) do
+ def perform(_job) do
if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()
diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
index 84b3b84de..e926c5dc8 100644
--- a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
+++ b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
@@ -20,7 +20,7 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
@interval :timer.minutes(1)
@impl Oban.Worker
- def perform(_opts, _job) do
+ def perform(_job) do
if Config.get([ActivityExpiration, :enabled]) do
Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
else
diff --git a/lib/pleroma/workers/cron/stats_worker.ex b/lib/pleroma/workers/cron/stats_worker.ex
index e9b8d59c4..e54bd9a7f 100644
--- a/lib/pleroma/workers/cron/stats_worker.ex
+++ b/lib/pleroma/workers/cron/stats_worker.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Workers.Cron.StatsWorker do
use Oban.Worker, queue: "background"
@impl Oban.Worker
- def perform(_opts, _job) do
+ def perform(_job) do
Pleroma.Stats.do_collect()
end
end
diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex
index 6955338a5..32273cfa5 100644
--- a/lib/pleroma/workers/mailer_worker.ex
+++ b/lib/pleroma/workers/mailer_worker.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Workers.MailerWorker do
use Pleroma.Workers.WorkerHelper, queue: "mailer"
@impl Oban.Worker
- def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do
+ def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
encoded_email
|> Base.decode64!()
|> :erlang.binary_to_term()
diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex
index daf79efc0..e739c3cd0 100644
--- a/lib/pleroma/workers/publisher_worker.ex
+++ b/lib/pleroma/workers/publisher_worker.ex
@@ -8,17 +8,17 @@ defmodule Pleroma.Workers.PublisherWorker do
use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
- def backoff(attempt) when is_integer(attempt) do
+ def backoff(%Job{attempt: attempt}) when is_integer(attempt) do
Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5)
end
@impl Oban.Worker
- def perform(%{"op" => "publish", "activity_id" => activity_id}, _job) do
+ def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
activity = Activity.get_by_id(activity_id)
Federator.perform(:publish, activity)
end
- def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do
+ def perform(%Job{args: %{"op" => "publish_one", "module" => module_name, "params" => params}}) do
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
Federator.perform(:publish_one, String.to_atom(module_name), params)
end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index f7a7124f3..1b97af1a8 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
use Pleroma.Workers.WorkerHelper, queue: "federator_incoming"
@impl Oban.Worker
- def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do
+ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
Federator.perform(:incoming_ap_doc, params)
end
end
diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex
index ec6534f21..27e2e3386 100644
--- a/lib/pleroma/workers/remote_fetcher_worker.ex
+++ b/lib/pleroma/workers/remote_fetcher_worker.ex
@@ -8,13 +8,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
@impl Oban.Worker
- def perform(
- %{
- "op" => "fetch_remote",
- "id" => id
- } = args,
- _job
- ) do
+ def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
end
end
diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex
index 97d1efbfb..dd9986fe4 100644
--- a/lib/pleroma/workers/scheduled_activity_worker.ex
+++ b/lib/pleroma/workers/scheduled_activity_worker.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do
require Logger
@impl Oban.Worker
- def perform(%{"activity_id" => activity_id}, _job) do
+ def perform(%Job{args: %{"activity_id" => activity_id}}) do
if Config.get([ScheduledActivity, :enabled]) do
case Pleroma.Repo.get(ScheduledActivity, activity_id) do
%ScheduledActivity{} = scheduled_activity ->
diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex
index 11239ca5e..15f36375c 100644
--- a/lib/pleroma/workers/transmogrifier_worker.ex
+++ b/lib/pleroma/workers/transmogrifier_worker.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Workers.TransmogrifierWorker do
use Pleroma.Workers.WorkerHelper, queue: "transmogrifier"
@impl Oban.Worker
- def perform(%{"op" => "user_upgrade", "user_id" => user_id}, _job) do
+ def perform(%Job{args: %{"op" => "user_upgrade", "user_id" => user_id}}) do
user = User.get_cached_by_id(user_id)
Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user)
end
diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex
index 58ad25e39..0cfdc6a6f 100644
--- a/lib/pleroma/workers/web_pusher_worker.ex
+++ b/lib/pleroma/workers/web_pusher_worker.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Workers.WebPusherWorker do
use Pleroma.Workers.WorkerHelper, queue: "web_push"
@impl Oban.Worker
- def perform(%{"op" => "web_push", "notification_id" => notification_id}, _job) do
+ def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do
notification =
Notification
|> Repo.get(notification_id)
diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex
index d1f90c35b..7d1289be2 100644
--- a/lib/pleroma/workers/worker_helper.ex
+++ b/lib/pleroma/workers/worker_helper.ex
@@ -32,6 +32,8 @@ defmodule Pleroma.Workers.WorkerHelper do
queue: unquote(queue),
max_attempts: 1
+ alias Oban.Job
+
def enqueue(op, params, worker_args \\ []) do
params = Map.merge(%{"op" => op}, params)
queue_atom = String.to_atom(unquote(queue))
@@ -39,7 +41,7 @@ defmodule Pleroma.Workers.WorkerHelper do
unquote(caller_module)
|> apply(:new, [params, worker_args])
- |> Pleroma.Repo.insert()
+ |> Oban.insert()
end
end
end
diff --git a/mix.exs b/mix.exs
index 4d13e95d7..e93dc7753 100644
--- a/mix.exs
+++ b/mix.exs
@@ -124,7 +124,7 @@ defmodule Pleroma.Mixfile do
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.3.2"},
{:postgrex, ">= 0.13.5"},
- {:oban, "~> 1.2"},
+ {:oban, "~> 2.0.0-rc.1"},
{:gettext, "~> 0.15"},
{:pbkdf2_elixir, "~> 1.0"},
{:bcrypt_elixir, "~> 2.0"},
diff --git a/mix.lock b/mix.lock
index 5383c2c6e..705e911f8 100644
--- a/mix.lock
+++ b/mix.lock
@@ -23,11 +23,11 @@
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"crypt": {:git, "https://github.com/msantos/crypt", "f63a705f92c26955977ee62a313012e309a4d77a", [ref: "f63a705f92c26955977ee62a313012e309a4d77a"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
- "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"},
+ "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
- "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"},
+ "ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
@@ -75,7 +75,7 @@
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
- "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
+ "oban": {:hex, :oban, "2.0.0-rc.1", "be0be1769578ff8da1818fd9685838d49bd9c83660cd593c48ac6633638171e0", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3ae0dacbd39babd82468f290073b5e58618df0cca1b48cc60d8c1ff1757d4c01"},
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
@@ -90,7 +90,7 @@
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm", "fec8660eb7733ee4117b85f55799fd3833eb769a6df71ccf8903e8dc5447cfce"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
- "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"},
+ "postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
"pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"},
"prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
diff --git a/test/activity_expiration_test.exs b/test/activity_expiration_test.exs
index e899d4509..d75c06cc7 100644
--- a/test/activity_expiration_test.exs
+++ b/test/activity_expiration_test.exs
@@ -44,7 +44,7 @@ defmodule Pleroma.ActivityExpirationTest do
%{activity_id: activity.id, scheduled_at: naive_datetime}
)
- Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(:ops, :pid)
+ Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{})
refute Pleroma.Repo.get(Pleroma.Activity, activity.id)
refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id)
diff --git a/test/support/oban_helpers.ex b/test/support/oban_helpers.ex
index e96994c57..9f90a821c 100644
--- a/test/support/oban_helpers.ex
+++ b/test/support/oban_helpers.ex
@@ -20,7 +20,7 @@ defmodule Pleroma.Tests.ObanHelpers do
end
def perform(%Oban.Job{} = job) do
- res = apply(String.to_existing_atom("Elixir." <> job.worker), :perform, [job.args, job])
+ res = apply(String.to_existing_atom("Elixir." <> job.worker), :perform, [job])
Repo.delete(job)
res
end
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 7693f6400..8a1cd6f12 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -1457,7 +1457,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
- Pleroma.Workers.BackgroundWorker.perform(params, nil)
+ Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
refute User.following?(follower, old_user)
assert User.following?(follower, new_user)
diff --git a/test/workers/cron/clear_oauth_token_worker_test.exs b/test/workers/cron/clear_oauth_token_worker_test.exs
index df82dc75d..67836f34f 100644
--- a/test/workers/cron/clear_oauth_token_worker_test.exs
+++ b/test/workers/cron/clear_oauth_token_worker_test.exs
@@ -16,7 +16,7 @@ defmodule Pleroma.Workers.Cron.ClearOauthTokenWorkerTest do
)
Pleroma.Config.put([:oauth2, :clean_expired_tokens], true)
- ClearOauthTokenWorker.perform(:opts, :job)
+ ClearOauthTokenWorker.perform(%Oban.Job{})
assert Pleroma.Repo.all(Pleroma.Web.OAuth.Token) == []
end
end
diff --git a/test/workers/cron/digest_emails_worker_test.exs b/test/workers/cron/digest_emails_worker_test.exs
index f9bc50db5..65887192e 100644
--- a/test/workers/cron/digest_emails_worker_test.exs
+++ b/test/workers/cron/digest_emails_worker_test.exs
@@ -35,7 +35,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorkerTest do
end
test "it sends digest emails", %{user2: user2} do
- Pleroma.Workers.Cron.DigestEmailsWorker.perform(:opts, :pid)
+ Pleroma.Workers.Cron.DigestEmailsWorker.perform(%Oban.Job{})
# Performing job(s) enqueued at previous step
ObanHelpers.perform_all()
@@ -47,7 +47,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorkerTest do
test "it doesn't fail when a user has no email", %{user2: user2} do
{:ok, _} = user2 |> Ecto.Changeset.change(%{email: nil}) |> Pleroma.Repo.update()
- Pleroma.Workers.Cron.DigestEmailsWorker.perform(:opts, :pid)
+ Pleroma.Workers.Cron.DigestEmailsWorker.perform(%Oban.Job{})
# Performing job(s) enqueued at previous step
ObanHelpers.perform_all()
end
diff --git a/test/workers/cron/new_users_digest_worker_test.exs b/test/workers/cron/new_users_digest_worker_test.exs
index ee589bb55..129534cb1 100644
--- a/test/workers/cron/new_users_digest_worker_test.exs
+++ b/test/workers/cron/new_users_digest_worker_test.exs
@@ -17,7 +17,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorkerTest do
user2 = insert(:user, %{inserted_at: yesterday})
CommonAPI.post(user, %{status: "cofe"})
- NewUsersDigestWorker.perform(nil, nil)
+ NewUsersDigestWorker.perform(%Oban.Job{})
ObanHelpers.perform_all()
assert_received {:email, email}
@@ -39,7 +39,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorkerTest do
CommonAPI.post(user, %{status: "cofe"})
- NewUsersDigestWorker.perform(nil, nil)
+ NewUsersDigestWorker.perform(%Oban.Job{})
ObanHelpers.perform_all()
end
end
diff --git a/test/workers/cron/purge_expired_activities_worker_test.exs b/test/workers/cron/purge_expired_activities_worker_test.exs
index 6d2991a60..5b2ffbc4c 100644
--- a/test/workers/cron/purge_expired_activities_worker_test.exs
+++ b/test/workers/cron/purge_expired_activities_worker_test.exs
@@ -33,7 +33,7 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorkerTest do
%{activity_id: activity.id, scheduled_at: naive_datetime}
)
- Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(:ops, :pid)
+ Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{})
refute Pleroma.Repo.get(Pleroma.Activity, activity.id)
refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id)
@@ -62,7 +62,7 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorkerTest do
|> Ecto.Changeset.change(%{scheduled_at: past_date})
|> Repo.update!()
- Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(:ops, :pid)
+ Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{})
assert [%{data: %{"type" => "Delete", "deleted_activity_id" => ^id}}] =
Pleroma.Repo.all(Pleroma.Activity)
diff --git a/test/workers/scheduled_activity_worker_test.exs b/test/workers/scheduled_activity_worker_test.exs
index b312d975b..f3eddf7b1 100644
--- a/test/workers/scheduled_activity_worker_test.exs
+++ b/test/workers/scheduled_activity_worker_test.exs
@@ -32,10 +32,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorkerTest do
params: %{status: "hi"}
)
- ScheduledActivityWorker.perform(
- %{"activity_id" => scheduled_activity.id},
- :pid
- )
+ ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => scheduled_activity.id}})
refute Repo.get(ScheduledActivity, scheduled_activity.id)
activity = Repo.all(Pleroma.Activity) |> Enum.find(&(&1.actor == user.ap_id))
@@ -46,7 +43,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorkerTest do
Pleroma.Config.put([ScheduledActivity, :enabled], true)
assert capture_log([level: :error], fn ->
- ScheduledActivityWorker.perform(%{"activity_id" => 42}, :pid)
+ ScheduledActivityWorker.perform(%Oban.Job{args: %{"activity_id" => 42}})
end) =~ "Couldn't find scheduled activity"
end
end
From 71e233268a290dcfba1b1bf1fdcb2eca4840f2d7 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Tue, 23 Jun 2020 21:47:01 +0300
Subject: [PATCH 005/213] oban 2.0-rc2
---
mix.exs | 4 ++--
mix.lock | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/mix.exs b/mix.exs
index e93dc7753..c7a811b9d 100644
--- a/mix.exs
+++ b/mix.exs
@@ -122,9 +122,9 @@ defmodule Pleroma.Mixfile do
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_enum, "~> 1.4"},
- {:ecto_sql, "~> 3.3.2"},
+ {:ecto_sql, "~> 3.4.4"},
{:postgrex, ">= 0.13.5"},
- {:oban, "~> 2.0.0-rc.1"},
+ {:oban, "~> 2.0.0-rc.2"},
{:gettext, "~> 0.15"},
{:pbkdf2_elixir, "~> 1.0"},
{:bcrypt_elixir, "~> 2.0"},
diff --git a/mix.lock b/mix.lock
index 705e911f8..639c54b4a 100644
--- a/mix.lock
+++ b/mix.lock
@@ -29,7 +29,7 @@
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
"ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
- "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
+ "ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
@@ -75,7 +75,7 @@
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
- "oban": {:hex, :oban, "2.0.0-rc.1", "be0be1769578ff8da1818fd9685838d49bd9c83660cd593c48ac6633638171e0", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3ae0dacbd39babd82468f290073b5e58618df0cca1b48cc60d8c1ff1757d4c01"},
+ "oban": {:hex, :oban, "2.0.0-rc.2", "4a3ba53af98a9aaeee7e53209bbdb18a80972952d4c2ccc6ac61ffd30fa96e8a", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a01ace5b6cd142fea547a554b7b752be7ea8fb08e7ffee57405d3b28561560c"},
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
@@ -106,7 +106,7 @@
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
- "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
+ "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
From cc837f9d157f9d43a015a8908f5e2ee178442041 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Wed, 24 Jun 2020 21:21:33 +0300
Subject: [PATCH 006/213] fixed config/descpiption.exs
---
config/description.exs | 9 +--------
lib/pleroma/application.ex | 14 +-------------
2 files changed, 2 insertions(+), 21 deletions(-)
diff --git a/config/description.exs b/config/description.exs
index f9523936a..ff777391e 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1996,18 +1996,11 @@ config :pleroma, :config_description, [
""",
children: [
%{
- key: :verbose,
+ key: :log,
type: {:dropdown, :atom},
description: "Logs verbose mode",
suggestions: [false, :error, :warn, :info, :debug]
},
- %{
- key: :prune,
- type: [:atom, :tuple],
- description:
- "Non-retryable jobs [pruning settings](https://github.com/sorentwo/oban#pruning)",
- suggestions: [:disabled, {:maxlen, 1500}, {:maxage, 60 * 60}]
- },
%{
key: :queues,
type: {:keyword, :integer},
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index fb2731f97..9615af122 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -80,7 +80,7 @@ defmodule Pleroma.Application do
[
Pleroma.Stats,
Pleroma.JobQueueMonitor,
- {Oban, oban_config()}
+ {Oban, Config.get(Oban)}
] ++
task_children(@env) ++
streamer_child(@env) ++
@@ -138,18 +138,6 @@ defmodule Pleroma.Application do
Pleroma.Web.Endpoint.Instrumenter.setup()
end
- defp oban_config do
- config = Config.get(Oban)
-
- if Code.ensure_loaded?(IEx) and IEx.started?() do
- config
- |> Keyword.put(:crontab, false)
- |> Keyword.put(:queues, false)
- else
- config
- end
- end
-
defp cachex_children do
[
build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
From f378e93bf4ca4bc9547f242e76e6258e25852972 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 26 Jun 2020 16:15:27 +0200
Subject: [PATCH 007/213] AccountController: Return scope in proper format.
---
lib/pleroma/web/api_spec/operations/account_operation.ex | 4 ++--
.../web/mastodon_api/controllers/account_controller.ex | 2 +-
.../mastodon_api/controllers/account_controller_test.exs | 6 +++---
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 9bde8fc0d..d94dae374 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -446,13 +446,13 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
properties: %{
token_type: %Schema{type: :string},
access_token: %Schema{type: :string},
- scope: %Schema{type: :array, items: %Schema{type: :string}},
+ scope: %Schema{type: :string},
created_at: %Schema{type: :integer, format: :"date-time"}
},
example: %{
"access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
"created_at" => 1_585_918_714,
- "scope" => ["read", "write", "follow", "push"],
+ "scope" => "read write follow push",
"token_type" => "Bearer"
}
}
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 7a88a847c..a87dddddf 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -104,7 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
json(conn, %{
token_type: "Bearer",
access_token: token.token,
- scope: app.scopes,
+ scope: app.scopes |> Enum.join(" "),
created_at: Token.Utils.format_created_at(token)
})
else
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index ebfcedd01..fcc1e792b 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -905,7 +905,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
%{
"access_token" => token,
"created_at" => _created_at,
- "scope" => _scope,
+ "scope" => ^scope,
"token_type" => "Bearer"
} = json_response_and_validate_schema(conn, 200)
@@ -1067,7 +1067,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert %{
"access_token" => access_token,
"created_at" => _,
- "scope" => ["read", "write", "follow", "push"],
+ "scope" => "read write follow push",
"token_type" => "Bearer"
} = response
@@ -1185,7 +1185,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert %{
"access_token" => access_token,
"created_at" => _,
- "scope" => ["read"],
+ "scope" => "read",
"token_type" => "Bearer"
} =
conn
From fd5e797379155e5a85deb88dc79f8fbca483948e Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 26 Jun 2020 11:24:28 -0500
Subject: [PATCH 008/213] Simplify notification filtering settings further
---
CHANGELOG.md | 5 +--
docs/API/pleroma_api.md | 4 +-
lib/pleroma/notification.ex | 28 ++----------
lib/pleroma/user/notification_setting.ex | 8 +---
lib/pleroma/web/api_spec/schemas/account.ex | 8 +---
...439_users_update_notification_settings.exs | 43 -------------------
test/notification_test.exs | 28 +-----------
.../mastodon_api/views/account_view_test.exs | 4 +-
test/web/twitter_api/util_controller_test.exs | 10 ++---
9 files changed, 15 insertions(+), 123 deletions(-)
delete mode 100644 priv/repo/migrations/20200528160439_users_update_notification_settings.exs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 82915dcfb..1d835fee2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,9 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
API Changes
- **Breaking:** Emoji API: changed methods and renamed routes.
-- **Breaking:** Notification Settings API for suppressing notification
- now supports the following controls: `from_followers`, `from_following`,
- and `from_strangers`.
+- **Breaking:** Notification Settings API for suppressing notifications
+ has been simplified down to `block_from_strangers`.
diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
index 9ad1f5c1b..6d8a88a44 100644
--- a/docs/API/pleroma_api.md
+++ b/docs/API/pleroma_api.md
@@ -287,9 +287,7 @@ See [Admin-API](admin_api.md)
* Method `PUT`
* Authentication: required
* Params:
- * `from_followers`: BOOLEAN field, receives notifications from followers
- * `from_following`: BOOLEAN field, receives notifications from people the user follows
- * `from_strangers`: BOOLEAN field, receives notifications from people without an established relationship
+ * `block_from_strangers`: BOOLEAN field, blocks notifications from accounts you do not follow
* `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 9d09cf082..8a28a1821 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -550,9 +550,7 @@ defmodule Pleroma.Notification do
[
:self,
:invisible,
- :from_followers,
- :from_following,
- :from_strangers,
+ :block_from_strangers,
:recently_followed
]
|> Enum.find(&skip?(&1, activity, user))
@@ -572,35 +570,15 @@ defmodule Pleroma.Notification do
end
def skip?(
- :from_followers,
+ :block_from_strangers,
%Activity{} = activity,
- %User{notification_settings: %{from_followers: false}} = user
- ) do
- actor = activity.data["actor"]
- follower = User.get_cached_by_ap_id(actor)
- User.following?(follower, user)
- end
-
- def skip?(
- :from_strangers,
- %Activity{} = activity,
- %User{notification_settings: %{from_strangers: false}} = user
+ %User{notification_settings: %{block_from_strangers: true}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user)
end
- def skip?(
- :from_following,
- %Activity{} = activity,
- %User{notification_settings: %{from_following: false}} = user
- ) do
- actor = activity.data["actor"]
- followed = User.get_cached_by_ap_id(actor)
- User.following?(user, followed)
- end
-
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
actor = activity.data["actor"]
diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex
index e47ac4cab..ffe9860de 100644
--- a/lib/pleroma/user/notification_setting.ex
+++ b/lib/pleroma/user/notification_setting.ex
@@ -10,18 +10,14 @@ defmodule Pleroma.User.NotificationSetting do
@primary_key false
embedded_schema do
- field(:from_followers, :boolean, default: true)
- field(:from_following, :boolean, default: true)
- field(:from_strangers, :boolean, default: true)
+ field(:block_from_strangers, :boolean, default: false)
field(:privacy_option, :boolean, default: false)
end
def changeset(schema, params) do
schema
|> cast(prepare_attrs(params), [
- :from_followers,
- :from_following,
- :from_strangers,
+ :block_from_strangers,
:privacy_option
])
end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index ed90ef3db..91bb1ba88 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -57,9 +57,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
notification_settings: %Schema{
type: :object,
properties: %{
- from_followers: %Schema{type: :boolean},
- from_following: %Schema{type: :boolean},
- from_strangers: %Schema{type: :boolean},
+ block_from_strangers: %Schema{type: :boolean},
privacy_option: %Schema{type: :boolean}
}
},
@@ -122,9 +120,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"unread_conversation_count" => 0,
"tags" => [],
"notification_settings" => %{
- "from_followers" => true,
- "from_following" => true,
- "from_strangers" => true,
+ "block_from_strangers" => false,
"privacy_option" => false
},
"relationship" => %{
diff --git a/priv/repo/migrations/20200528160439_users_update_notification_settings.exs b/priv/repo/migrations/20200528160439_users_update_notification_settings.exs
deleted file mode 100644
index 561f7a2c4..000000000
--- a/priv/repo/migrations/20200528160439_users_update_notification_settings.exs
+++ /dev/null
@@ -1,43 +0,0 @@
-defmodule Pleroma.Repo.Migrations.UsersUpdateNotificationSettings do
- use Ecto.Migration
-
- def up do
- execute(
- "UPDATE users SET notification_settings = notification_settings - 'followers' || jsonb_build_object('from_followers', notification_settings->'followers')
-where notification_settings ? 'followers'
-and local"
- )
-
- execute(
- "UPDATE users SET notification_settings = notification_settings - 'follows' || jsonb_build_object('from_following', notification_settings->'follows')
-where notification_settings ? 'follows'
-and local"
- )
-
- execute(
- "UPDATE users SET notification_settings = notification_settings - 'non_followers' || jsonb_build_object('from_strangers', notification_settings->'non_followers')
-where notification_settings ? 'non_followers'
-and local"
- )
- end
-
- def down do
- execute(
- "UPDATE users SET notification_settings = notification_settings - 'from_followers' || jsonb_build_object('followers', notification_settings->'from_followers')
-where notification_settings ? 'from_followers'
-and local"
- )
-
- execute(
- "UPDATE users SET notification_settings = notification_settings - 'from_following' || jsonb_build_object('follows', notification_settings->'from_following')
-where notification_settings ? 'from_following'
-and local"
- )
-
- execute(
- "UPDATE users SET notification_settings = notification_settings - 'from_strangers' || jsonb_build_object('non_follows', notification_settings->'from_strangers')
-where notification_settings ? 'from_strangers'
-and local"
- )
- end
-end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index d7df9c36c..d8cb9360a 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -236,44 +236,18 @@ defmodule Pleroma.NotificationTest do
assert Notification.create_notification(activity, muter)
end
- test "it disables notifications from followers" do
- follower = insert(:user)
-
- followed =
- insert(:user,
- notification_settings: %Pleroma.User.NotificationSetting{from_followers: false}
- )
-
- User.follow(follower, followed)
- {:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
- refute Notification.create_notification(activity, followed)
- end
-
test "it disables notifications from strangers" do
follower = insert(:user)
followed =
insert(:user,
- notification_settings: %Pleroma.User.NotificationSetting{from_strangers: false}
+ notification_settings: %Pleroma.User.NotificationSetting{block_from_strangers: true}
)
{:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
refute Notification.create_notification(activity, followed)
end
- test "it disables notifications from people the user follows" do
- follower =
- insert(:user,
- notification_settings: %Pleroma.User.NotificationSetting{from_following: false}
- )
-
- followed = insert(:user)
- User.follow(follower, followed)
- follower = Repo.get(User, follower.id)
- {:ok, activity} = CommonAPI.post(followed, %{status: "hey @#{follower.nickname}"})
- refute Notification.create_notification(activity, follower)
- end
-
test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 572830194..b6d820b3f 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -96,9 +96,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
user = insert(:user)
notification_settings = %{
- from_followers: true,
- from_following: true,
- from_strangers: true,
+ block_from_strangers: false,
privacy_option: false
}
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index 1133107f4..da3f6fa61 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -191,7 +191,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
test "it updates notification settings", %{user: user, conn: conn} do
conn
|> put("/api/pleroma/notification_settings", %{
- "from_followers" => false,
+ "block_from_strangers" => true,
"bar" => 1
})
|> json_response(:ok)
@@ -199,9 +199,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
user = refresh_record(user)
assert %Pleroma.User.NotificationSetting{
- from_followers: false,
- from_following: true,
- from_strangers: true,
+ block_from_strangers: true,
privacy_option: false
} == user.notification_settings
end
@@ -214,9 +212,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
user = refresh_record(user)
assert %Pleroma.User.NotificationSetting{
- from_followers: true,
- from_following: true,
- from_strangers: true,
+ block_from_strangers: false,
privacy_option: true
} == user.notification_settings
end
From 69848d5c97c9e5d4c14fb5613eb174cb81d5026d Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 26 Jun 2020 12:45:46 -0500
Subject: [PATCH 009/213] Rename notification "privacy_option" setting
---
docs/API/pleroma_api.md | 2 +-
.../tasks/pleroma/notification_settings.ex | 18 +++++++++---------
lib/pleroma/user/notification_setting.ex | 4 ++--
lib/pleroma/web/api_spec/schemas/account.ex | 4 ++--
lib/pleroma/web/push/impl.ex | 2 +-
...359_rename_notification_privacy_option.exs | 19 +++++++++++++++++++
test/user/notification_setting_test.exs | 4 ++--
.../mastodon_api/views/account_view_test.exs | 2 +-
test/web/push/impl_test.exs | 8 ++++----
test/web/twitter_api/util_controller_test.exs | 8 ++++----
10 files changed, 45 insertions(+), 26 deletions(-)
create mode 100644 priv/repo/migrations/20200626163359_rename_notification_privacy_option.exs
diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md
index 6d8a88a44..5bd38ad36 100644
--- a/docs/API/pleroma_api.md
+++ b/docs/API/pleroma_api.md
@@ -288,7 +288,7 @@ See [Admin-API](admin_api.md)
* Authentication: required
* Params:
* `block_from_strangers`: BOOLEAN field, blocks notifications from accounts you do not follow
- * `privacy_option`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
+ * `hide_notification_contents`: BOOLEAN field. When set to true, it removes the contents of a message from the push notification.
* Response: JSON. Returns `{"status": "success"}` if the update was successful, otherwise returns `{"error": "error_msg"}`
## `/api/pleroma/healthcheck`
diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex
index 7d65f0587..00f5ba7bf 100644
--- a/lib/mix/tasks/pleroma/notification_settings.ex
+++ b/lib/mix/tasks/pleroma/notification_settings.ex
@@ -3,8 +3,8 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
@moduledoc """
Example:
- > mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588" # set false only for parallel588 user
- > mix pleroma.notification_settings --privacy-option=true # set true for all users
+ > mix pleroma.notification_settings --hide-notification-contents=false --nickname-users="parallel588" # set false only for parallel588 user
+ > mix pleroma.notification_settings --hide-notification-contents=true # set true for all users
"""
@@ -19,16 +19,16 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
OptionParser.parse(
args,
strict: [
- privacy_option: :boolean,
+ hide_notification_contents: :boolean,
email_users: :string,
nickname_users: :string
]
)
- privacy_option = Keyword.get(options, :privacy_option)
+ hide_notification_contents = Keyword.get(options, :hide_notification_contents)
- if not is_nil(privacy_option) do
- privacy_option
+ if not is_nil(hide_notification_contents) do
+ hide_notification_contents
|> build_query(options)
|> Pleroma.Repo.update_all([])
end
@@ -36,15 +36,15 @@ defmodule Mix.Tasks.Pleroma.NotificationSettings do
shell_info("Done")
end
- defp build_query(privacy_option, options) do
+ defp build_query(hide_notification_contents, options) do
query =
from(u in Pleroma.User,
update: [
set: [
notification_settings:
fragment(
- "jsonb_set(notification_settings, '{privacy_option}', ?)",
- ^privacy_option
+ "jsonb_set(notification_settings, '{hide_notification_contents}', ?)",
+ ^hide_notification_contents
)
]
]
diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex
index ffe9860de..7d9e8a000 100644
--- a/lib/pleroma/user/notification_setting.ex
+++ b/lib/pleroma/user/notification_setting.ex
@@ -11,14 +11,14 @@ defmodule Pleroma.User.NotificationSetting do
embedded_schema do
field(:block_from_strangers, :boolean, default: false)
- field(:privacy_option, :boolean, default: false)
+ field(:hide_notification_contents, :boolean, default: false)
end
def changeset(schema, params) do
schema
|> cast(prepare_attrs(params), [
:block_from_strangers,
- :privacy_option
+ :hide_notification_contents
])
end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 91bb1ba88..71d402b18 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -58,7 +58,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
type: :object,
properties: %{
block_from_strangers: %Schema{type: :boolean},
- privacy_option: %Schema{type: :boolean}
+ hide_notification_contents: %Schema{type: :boolean}
}
},
relationship: AccountRelationship,
@@ -121,7 +121,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"tags" => [],
"notification_settings" => %{
"block_from_strangers" => false,
- "privacy_option" => false
+ "hide_notification_contents" => false
},
"relationship" => %{
"blocked_by" => false,
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index cdb827e76..16368485e 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -104,7 +104,7 @@ defmodule Pleroma.Web.Push.Impl do
def build_content(
%{
- user: %{notification_settings: %{privacy_option: true}}
+ user: %{notification_settings: %{hide_notification_contents: true}}
} = notification,
_actor,
_object,
diff --git a/priv/repo/migrations/20200626163359_rename_notification_privacy_option.exs b/priv/repo/migrations/20200626163359_rename_notification_privacy_option.exs
new file mode 100644
index 000000000..06d7f7272
--- /dev/null
+++ b/priv/repo/migrations/20200626163359_rename_notification_privacy_option.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.RenameNotificationPrivacyOption do
+ use Ecto.Migration
+
+ def up do
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'privacy_option' || jsonb_build_object('hide_notification_contents', notification_settings->'privacy_option')
+where notification_settings ? 'privacy_option'
+and local"
+ )
+ end
+
+ def down do
+ execute(
+ "UPDATE users SET notification_settings = notification_settings - 'hide_notification_contents' || jsonb_build_object('privacy_option', notification_settings->'hide_notification_contents')
+where notification_settings ? 'hide_notification_contents'
+and local"
+ )
+ end
+end
diff --git a/test/user/notification_setting_test.exs b/test/user/notification_setting_test.exs
index 95bca22c4..308da216a 100644
--- a/test/user/notification_setting_test.exs
+++ b/test/user/notification_setting_test.exs
@@ -8,11 +8,11 @@ defmodule Pleroma.User.NotificationSettingTest do
alias Pleroma.User.NotificationSetting
describe "changeset/2" do
- test "sets valid privacy option" do
+ test "sets option to hide notification contents" do
changeset =
NotificationSetting.changeset(
%NotificationSetting{},
- %{"privacy_option" => true}
+ %{"hide_notification_contents" => true}
)
assert %Ecto.Changeset{valid?: true} = changeset
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index b6d820b3f..ce45cb9e9 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -97,7 +97,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
notification_settings = %{
block_from_strangers: false,
- privacy_option: false
+ hide_notification_contents: false
}
privacy = user.default_scope
diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs
index b48952b29..15de5e853 100644
--- a/test/web/push/impl_test.exs
+++ b/test/web/push/impl_test.exs
@@ -238,9 +238,9 @@ defmodule Pleroma.Web.Push.ImplTest do
}
end
- test "hides details for notifications when privacy option enabled" do
+ test "hides contents of notifications when option enabled" do
user = insert(:user, nickname: "Bob")
- user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true})
+ user2 = insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: true})
{:ok, activity} =
CommonAPI.post(user, %{
@@ -284,9 +284,9 @@ defmodule Pleroma.Web.Push.ImplTest do
}
end
- test "returns regular content for notifications with privacy option disabled" do
+ test "returns regular content when hiding contents option disabled" do
user = insert(:user, nickname: "Bob")
- user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: false})
+ user2 = insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: false})
{:ok, activity} =
CommonAPI.post(user, %{
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index da3f6fa61..b8ddadb50 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -200,20 +200,20 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
assert %Pleroma.User.NotificationSetting{
block_from_strangers: true,
- privacy_option: false
+ hide_notification_contents: false
} == user.notification_settings
end
- test "it updates notification privacy option", %{user: user, conn: conn} do
+ test "it updates notification settings to enable hiding contents", %{user: user, conn: conn} do
conn
- |> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"})
+ |> put("/api/pleroma/notification_settings", %{"hide_notification_contents" => "1"})
|> json_response(:ok)
user = refresh_record(user)
assert %Pleroma.User.NotificationSetting{
block_from_strangers: false,
- privacy_option: true
+ hide_notification_contents: true
} == user.notification_settings
end
end
From 76313e81627f4563ba2d3bf9f7bb5e6b8a20975b Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 26 Jun 2020 12:48:05 -0500
Subject: [PATCH 010/213] Document breaking change of hide_notification_details
setting
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d835fee2..1d640f292 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking:** Emoji API: changed methods and renamed routes.
- **Breaking:** Notification Settings API for suppressing notifications
has been simplified down to `block_from_strangers`.
+- **Breaking:** Notification Settings API option for hiding push notification
+ contents has been renamed to `hide_notification_contents`
From ce85db41a30d95555bbd44d8931c4a3a357938d8 Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 26 Jun 2020 14:35:04 -0500
Subject: [PATCH 011/213] Lint
---
test/web/push/impl_test.exs | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs
index 15de5e853..aeb5c1fbd 100644
--- a/test/web/push/impl_test.exs
+++ b/test/web/push/impl_test.exs
@@ -240,7 +240,9 @@ defmodule Pleroma.Web.Push.ImplTest do
test "hides contents of notifications when option enabled" do
user = insert(:user, nickname: "Bob")
- user2 = insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: true})
+
+ user2 =
+ insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: true})
{:ok, activity} =
CommonAPI.post(user, %{
@@ -286,7 +288,9 @@ defmodule Pleroma.Web.Push.ImplTest do
test "returns regular content when hiding contents option disabled" do
user = insert(:user, nickname: "Bob")
- user2 = insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: false})
+
+ user2 =
+ insert(:user, nickname: "Rob", notification_settings: %{hide_notification_contents: false})
{:ok, activity} =
CommonAPI.post(user, %{
From bb168ed94a6b4d02879472e30149a494d7b7ebb5 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 29 Jun 2020 13:39:09 +0200
Subject: [PATCH 012/213] OAuth: Extract view-type functions to a view.
---
lib/pleroma/web/oauth/mfa_controller.ex | 3 +-
lib/pleroma/web/oauth/mfa_view.ex | 9 ++++++
lib/pleroma/web/oauth/oauth_controller.ex | 18 +++++------
lib/pleroma/web/oauth/oauth_view.ex | 22 +++++++++++++
lib/pleroma/web/oauth/token/response.ex | 39 -----------------------
5 files changed, 41 insertions(+), 50 deletions(-)
diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/oauth/mfa_controller.ex
index 53e19f82e..f102c93e7 100644
--- a/lib/pleroma/web/oauth/mfa_controller.ex
+++ b/lib/pleroma/web/oauth/mfa_controller.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
alias Pleroma.Web.Auth.TOTPAuthenticator
alias Pleroma.Web.OAuth.MFAView, as: View
alias Pleroma.Web.OAuth.OAuthController
+ alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.OAuth.Token
plug(:fetch_session when action in [:show, :verify])
@@ -74,7 +75,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
{:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
{:ok, _} <- validates_challenge(user, params),
{:ok, token} <- Token.exchange_token(app, auth) do
- json(conn, Token.Response.build(user, token))
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
_error ->
conn
diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/oauth/mfa_view.ex
index 41d5578dc..5d87db268 100644
--- a/lib/pleroma/web/oauth/mfa_view.ex
+++ b/lib/pleroma/web/oauth/mfa_view.ex
@@ -5,4 +5,13 @@
defmodule Pleroma.Web.OAuth.MFAView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ alias Pleroma.MFA
+
+ def render("mfa_response.json", %{token: token, user: user}) do
+ %{
+ error: "mfa_required",
+ mfa_token: token.token,
+ supported_challenge_types: MFA.supported_methods(user)
+ }
+ end
end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index c557778ca..3da104933 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -6,8 +6,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller
alias Pleroma.Helpers.UriHelper
- alias Pleroma.Maps
alias Pleroma.MFA
+ alias Pleroma.Maps
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Registration
alias Pleroma.Repo
@@ -17,6 +17,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.MFAController
+ alias Pleroma.Web.OAuth.OAuthView
+ alias Pleroma.Web.OAuth.MFAView
alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
@@ -233,9 +235,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
{:ok, token} <- RefreshToken.grant(token) do
- response_attrs = %{created_at: Token.Utils.format_created_at(token)}
-
- json(conn, Token.Response.build(user, token, response_attrs))
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
_error -> render_invalid_credentials_error(conn)
end
@@ -247,9 +247,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth) do
- response_attrs = %{created_at: Token.Utils.format_created_at(token)}
-
- json(conn, Token.Response.build(user, token, response_attrs))
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
error ->
handle_token_exchange_error(conn, error)
@@ -267,7 +265,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
{:ok, token} <- Token.exchange_token(app, auth) do
- json(conn, Token.Response.build(user, token))
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
error ->
handle_token_exchange_error(conn, error)
@@ -290,7 +288,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
{:ok, token} <- Token.exchange_token(app, auth) do
- json(conn, Token.Response.build_for_client_credentials(token))
+ json(conn, OAuthView.render("token.json", %{token: token}))
else
_error ->
handle_token_exchange_error(conn, :invalid_credentails)
@@ -548,7 +546,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
defp build_and_response_mfa_token(user, auth) do
with {:ok, token} <- MFA.Token.create_token(user, auth) do
- Token.Response.build_for_mfa_token(user, token)
+ MFAView.render("mfa_response.json", %{token: token, user: user})
end
end
diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex
index 94ddaf913..f55247ebd 100644
--- a/lib/pleroma/web/oauth/oauth_view.ex
+++ b/lib/pleroma/web/oauth/oauth_view.ex
@@ -5,4 +5,26 @@
defmodule Pleroma.Web.OAuth.OAuthView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+
+ alias Pleroma.Web.OAuth.Token.Utils
+
+ def render("token.json", %{token: token} = opts) do
+ response = %{
+ token_type: "Bearer",
+ access_token: token.token,
+ refresh_token: token.refresh_token,
+ expires_in: expires_in(),
+ scope: Enum.join(token.scopes, " "),
+ created_at: Utils.format_created_at(token)
+ }
+
+ if user = opts[:user] do
+ response
+ |> Map.put(:me, user.ap_id)
+ else
+ response
+ end
+ end
+
+ defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end
diff --git a/lib/pleroma/web/oauth/token/response.ex b/lib/pleroma/web/oauth/token/response.ex
index 0e72c31e9..a12a6865c 100644
--- a/lib/pleroma/web/oauth/token/response.ex
+++ b/lib/pleroma/web/oauth/token/response.ex
@@ -3,43 +3,4 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Response do
- @moduledoc false
-
- alias Pleroma.MFA
- alias Pleroma.User
- alias Pleroma.Web.OAuth.Token.Utils
-
- @doc false
- def build(%User{} = user, token, opts \\ %{}) do
- %{
- token_type: "Bearer",
- access_token: token.token,
- refresh_token: token.refresh_token,
- expires_in: expires_in(),
- scope: Enum.join(token.scopes, " "),
- me: user.ap_id
- }
- |> Map.merge(opts)
- end
-
- def build_for_client_credentials(token) do
- %{
- token_type: "Bearer",
- access_token: token.token,
- refresh_token: token.refresh_token,
- created_at: Utils.format_created_at(token),
- expires_in: expires_in(),
- scope: Enum.join(token.scopes, " ")
- }
- end
-
- def build_for_mfa_token(user, mfa_token) do
- %{
- error: "mfa_required",
- mfa_token: mfa_token.token,
- supported_challenge_types: MFA.supported_methods(user)
- }
- end
-
- defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end
From e374872fe7d10aa659723ee31003f3e9188edfdd Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 29 Jun 2020 13:49:48 +0200
Subject: [PATCH 013/213] AccountOperation: Correctly describe create response.
---
.../web/api_spec/operations/account_operation.ex | 11 +++++++++--
.../mastodon_api/controllers/account_controller.ex | 8 ++------
2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index d94dae374..f3ffa1ad4 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -438,6 +438,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
+ # TODO: This is actually a token respone, but there's no oauth operation file yet.
defp create_response do
%Schema{
title: "AccountCreateResponse",
@@ -446,14 +447,20 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
properties: %{
token_type: %Schema{type: :string},
access_token: %Schema{type: :string},
+ refresh_token: %Schema{type: :string},
scope: %Schema{type: :string},
- created_at: %Schema{type: :integer, format: :"date-time"}
+ created_at: %Schema{type: :integer, format: :"date-time"},
+ me: %Schema{type: :string},
+ expires_in: %Schema{type: :integer}
},
example: %{
+ "token_type" => "Bearer",
"access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
+ "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
"created_at" => 1_585_918_714,
+ "expires_in" => 600,
"scope" => "read write follow push",
- "token_type" => "Bearer"
+ "me" => "https://gensokyo.2hu/users/raymoo"
}
}
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index a87dddddf..a143675ec 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -28,6 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.ApiSpec.CastAndValidate)
@@ -101,12 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:ok <- TwitterAPI.validate_captcha(app, params),
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
- json(conn, %{
- token_type: "Bearer",
- access_token: token.token,
- scope: app.scopes |> Enum.join(" "),
- created_at: Token.Utils.format_created_at(token)
- })
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
{:error, error} -> json_response(conn, :bad_request, %{error: error})
end
From f308196b7528fab92b3cfba12ea71c464e2f9ab0 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 29 Jun 2020 13:52:50 +0200
Subject: [PATCH 014/213] Token Response: Remove empty file.
---
lib/pleroma/web/oauth/token/response.ex | 6 ------
1 file changed, 6 deletions(-)
delete mode 100644 lib/pleroma/web/oauth/token/response.ex
diff --git a/lib/pleroma/web/oauth/token/response.ex b/lib/pleroma/web/oauth/token/response.ex
deleted file mode 100644
index a12a6865c..000000000
--- a/lib/pleroma/web/oauth/token/response.ex
+++ /dev/null
@@ -1,6 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.Response do
-end
From 59540131c189afb10faf98d1bfeccf8f94985a90 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 29 Jun 2020 14:09:03 +0200
Subject: [PATCH 015/213] Credo fixes.
---
.../web/mastodon_api/controllers/account_controller.ex | 2 +-
lib/pleroma/web/oauth/oauth_controller.ex | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index a143675ec..2942ed336 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView
- alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.OAuthView
+ alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 3da104933..7683589cf 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -6,8 +6,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller
alias Pleroma.Helpers.UriHelper
- alias Pleroma.MFA
alias Pleroma.Maps
+ alias Pleroma.MFA
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Registration
alias Pleroma.Repo
@@ -17,8 +17,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.MFAController
- alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.OAuth.MFAView
+ alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
From 8daacc911498d827fd68ea3d34eb1be9ae4a1ffe Mon Sep 17 00:00:00 2001
From: Alex Gleason
Date: Tue, 23 Jun 2020 14:17:23 -0500
Subject: [PATCH 016/213] AutoLinker --> Linkify, update to latest version
https://git.pleroma.social/pleroma/elixir-libraries/linkify
---
CHANGELOG.md | 1 +
config/config.exs | 18 ++++++--------
config/description.exs | 26 +++++++++++++-------
docs/configuration/cheatsheet.md | 35 +++++++++++++--------------
lib/pleroma/config/config_db.ex | 2 +-
lib/pleroma/formatter.ex | 26 +++++++++++---------
lib/pleroma/web/rich_media/helpers.ex | 4 +--
mix.exs | 4 +--
mix.lock | 2 +-
9 files changed, 63 insertions(+), 55 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71963d206..4d3bda99e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
+- **Breaking:** Configuration: `:auto_linker, :opts` moved to `:pleroma, Pleroma.Formatter`. Old config namespace is deprecated.
API Changes
diff --git a/config/config.exs b/config/config.exs
index 5aad26e95..a74a6a5ba 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -520,16 +520,14 @@ config :pleroma, :workers,
federator_outgoing: 5
]
-config :auto_linker,
- opts: [
- extra: true,
- # TODO: Set to :no_scheme when it works properly
- validate_tld: true,
- class: false,
- strip_prefix: false,
- new_window: false,
- rel: "ugc"
- ]
+config :pleroma, Pleroma.Formatter,
+ class: false,
+ rel: "ugc",
+ new_window: false,
+ truncate: false,
+ strip_prefix: false,
+ extra: true,
+ validate_tld: :no_scheme
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
diff --git a/config/description.exs b/config/description.exs
index f54ac2a2a..204de8324 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -2157,44 +2157,52 @@ config :pleroma, :config_description, [
]
},
%{
- group: :auto_linker,
- key: :opts,
+ group: :pleroma,
+ key: Pleroma.Formatter,
type: :group,
- description: "Configuration for the auto_linker library",
+ description:
+ "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.",
children: [
%{
key: :class,
type: [:string, false],
- description: "Specify the class to be added to the generated link. Disable to clear",
+ description: "Specify the class to be added to the generated link. Disable to clear.",
suggestions: ["auto-linker", false]
},
%{
key: :rel,
type: [:string, false],
- description: "Override the rel attribute. Disable to clear",
+ description: "Override the rel attribute. Disable to clear.",
suggestions: ["ugc", "noopener noreferrer", false]
},
%{
key: :new_window,
type: :boolean,
- description: "Link urls will open in new window/tab"
+ description: "Link URLs will open in new window/tab."
},
%{
key: :truncate,
type: [:integer, false],
description:
- "Set to a number to truncate urls longer then the number. Truncated urls will end in `..`",
+ "Set to a number to truncate URLs longer then the number. Truncated URLs will end in `...`",
suggestions: [15, false]
},
%{
key: :strip_prefix,
type: :boolean,
- description: "Strip the scheme prefix"
+ description: "Strip the scheme prefix."
},
%{
key: :extra,
type: :boolean,
- description: "Link urls with rarely used schemes (magnet, ipfs, irc, etc.)"
+ description: "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
+ },
+ %{
+ key: :validate_tld,
+ type: [:atom, :boolean],
+ description:
+ "Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for URLs without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't)",
+ suggestions: [:no_scheme, true]
}
]
},
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 6759d5e93..22b28d423 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -908,30 +908,29 @@ Configure OAuth 2 provider capabilities:
### :uri_schemes
* `valid_schemes`: List of the scheme part that is considered valid to be an URL.
-### :auto_linker
+### Pleroma.Formatter
-Configuration for the `auto_linker` library:
+Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs.
-* `class: "auto-linker"` - specify the class to be added to the generated link. false to clear.
-* `rel: "noopener noreferrer"` - override the rel attribute. false to clear.
-* `new_window: true` - set to false to remove `target='_blank'` attribute.
-* `scheme: false` - Set to true to link urls with schema `http://google.com`.
-* `truncate: false` - Set to a number to truncate urls longer then the number. Truncated urls will end in `..`.
-* `strip_prefix: true` - Strip the scheme prefix.
-* `extra: false` - link urls with rarely used schemes (magnet, ipfs, irc, etc.).
+* `class` - specify the class to be added to the generated link (default: `false`)
+* `rel` - specify the rel attribute (default: `ugc`)
+* `new_window` - adds `target="_blank"` attribute (default: `false`)
+* `truncate` - Set to a number to truncate URLs longer then the number. Truncated URLs will end in `...` (default: `false`)
+* `strip_prefix` - Strip the scheme prefix (default: `false`)
+* `extra` - link URLs with rarely used schemes (magnet, ipfs, irc, etc.) (default: `true`)
+* `validate_tld` - Set to false to disable TLD validation for URLs/emails. Can be set to :no_scheme to validate TLDs only for urls without a scheme (e.g `example.com` will be validated, but `http://example.loki` won't) (default: `:no_scheme`)
Example:
```elixir
-config :auto_linker,
- opts: [
- scheme: true,
- extra: true,
- class: false,
- strip_prefix: false,
- new_window: false,
- rel: "ugc"
- ]
+config :pleroma, Pleroma.Formatter,
+ class: false,
+ rel: "ugc",
+ new_window: false,
+ truncate: false,
+ strip_prefix: false,
+ extra: true,
+ validate_tld: :no_scheme
```
## Custom Runtime Modules (`:modules`)
diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex
index 1a89d8895..f8141ced8 100644
--- a/lib/pleroma/config/config_db.ex
+++ b/lib/pleroma/config/config_db.ex
@@ -156,7 +156,7 @@ defmodule Pleroma.ConfigDB do
{:quack, :meta},
{:mime, :types},
{:cors_plug, [:max_age, :methods, :expose, :headers]},
- {:auto_linker, :opts},
+ {:linkify, :opts},
{:swarm, :node_blacklist},
{:logger, :backends}
]
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 02a93a8dc..0c450eae4 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -10,11 +10,15 @@ defmodule Pleroma.Formatter do
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
- @auto_linker_config hashtag: true,
- hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
- mention: true,
- mention_handler: &Pleroma.Formatter.mention_handler/4,
- scheme: true
+ defp linkify_opts do
+ Pleroma.Config.get(Pleroma.Formatter) ++
+ [
+ hashtag: true,
+ hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
+ mention: true,
+ mention_handler: &Pleroma.Formatter.mention_handler/4
+ ]
+ end
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
case User.get_cached_by_nickname(nickname) do
@@ -80,19 +84,19 @@ defmodule Pleroma.Formatter do
@spec linkify(String.t(), keyword()) ::
{String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}
def linkify(text, options \\ []) do
- options = options ++ @auto_linker_config
+ options = linkify_opts() ++ options
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
- {text_mentions, %{mentions: mentions}} = AutoLinker.link_map(mentions, acc, options)
- {text_rest, %{tags: tags}} = AutoLinker.link_map(rest, acc, options)
+ {text_mentions, %{mentions: mentions}} = Linkify.link_map(mentions, acc, options)
+ {text_rest, %{tags: tags}} = Linkify.link_map(rest, acc, options)
{text_mentions <> text_rest, MapSet.to_list(mentions), MapSet.to_list(tags)}
else
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
- {text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)
+ {text, %{mentions: mentions, tags: tags}} = Linkify.link_map(text, acc, options)
{text, MapSet.to_list(mentions), MapSet.to_list(tags)}
end
@@ -111,9 +115,9 @@ defmodule Pleroma.Formatter do
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
- AutoLinker.link(mentions, options) <> AutoLinker.link(rest, options)
+ Linkify.link(mentions, options) <> Linkify.link(rest, options)
else
- AutoLinker.link(text, options)
+ Linkify.link(text, options)
end
end
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index 1729141e9..747f2dc6b 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -11,10 +11,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
defp validate_page_url(page_url) when is_binary(page_url) do
- validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
+ validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld])
page_url
- |> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld)
+ |> Linkify.Parser.url?(validate_tld: validate_tld)
|> parse_uri(page_url)
end
diff --git a/mix.exs b/mix.exs
index b638be541..c773a3162 100644
--- a/mix.exs
+++ b/mix.exs
@@ -167,9 +167,7 @@ defmodule Pleroma.Mixfile do
{:floki, "~> 0.25"},
{:timex, "~> 3.5"},
{:ueberauth, "~> 0.4"},
- {:auto_linker,
- git: "https://git.pleroma.social/pleroma/auto_linker.git",
- ref: "95e8188490e97505c56636c1379ffdf036c1fdde"},
+ {:linkify, "~> 0.1.0"},
{:http_signatures,
git: "https://git.pleroma.social/pleroma/http_signatures.git",
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
diff --git a/mix.lock b/mix.lock
index 5ad49391d..458cda6cf 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,6 +1,5 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
- "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]},
"base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
@@ -62,6 +61,7 @@
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
"libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"},
+ "linkify": {:hex, :linkify, "0.1.0", "a2d35de64271c7fbbc7d8773adb9f595592b7fbaa581271c7733f39d3058bfa4", [:mix], [], "hexpm", "d3140ef8dbdcc53ef93a6a5374c11fffe0189f00d132161e9d020a417780bee7"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
From 8ad166e8e385b7baea79dc3949b438edba25c69f Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 12:46:28 +0200
Subject: [PATCH 017/213] Migrations: Add `accepts_chat_messages` to users.
---
.../20200703101031_add_chat_acceptance_to_users.exs | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs
diff --git a/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs b/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs
new file mode 100644
index 000000000..4ae3c4201
--- /dev/null
+++ b/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs
@@ -0,0 +1,12 @@
+defmodule Pleroma.Repo.Migrations.AddChatAcceptanceToUsers do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ add(:accepts_chat_messages, :boolean, nullable: false, default: false)
+ end
+
+ # Looks stupid but makes the update much faster
+ execute("update users set accepts_chat_messages = local where local = true")
+ end
+end
From 98bfdba108d4213eea82dc4d63edb8bb834118fb Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 12:47:05 +0200
Subject: [PATCH 018/213] User: On registration, set `accepts_chat_messages` to
true.
---
lib/pleroma/user.ex | 5 ++++-
test/user_test.exs | 9 +++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 8a54546d6..79e094a79 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -138,6 +138,7 @@ defmodule Pleroma.User do
field(:also_known_as, {:array, :string}, default: [])
field(:inbox, :string)
field(:shared_inbox, :string)
+ field(:accepts_chat_messages, :boolean, default: false)
embeds_one(
:notification_settings,
@@ -623,6 +624,7 @@ defmodule Pleroma.User do
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ params = Map.put_new(params, :accepts_chat_messages, true)
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
@@ -641,7 +643,8 @@ defmodule Pleroma.User do
:nickname,
:password,
:password_confirmation,
- :emoji
+ :emoji,
+ :accepts_chat_messages
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
diff --git a/test/user_test.exs b/test/user_test.exs
index 7126bb539..9788e09d9 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -486,6 +486,15 @@ defmodule Pleroma.UserTest do
}
setup do: clear_config([:instance, :account_activation_required], true)
+ test "it sets the 'accepts_chat_messages' set to true" do
+ changeset = User.register_changeset(%User{}, @full_user_data)
+ assert changeset.valid?
+
+ {:ok, user} = Repo.insert(changeset)
+
+ assert user.accepts_chat_messages
+ end
+
test "it creates unconfirmed user" do
changeset = User.register_changeset(%User{}, @full_user_data)
assert changeset.valid?
From 3250228be9719b0afa24c97b64f56d2275c4fe67 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 13:07:33 +0200
Subject: [PATCH 019/213] AccountView: Add 'accepts_chat_messages' to view.
---
lib/pleroma/web/mastodon_api/views/account_view.ex | 3 ++-
test/web/mastodon_api/views/account_view_test.exs | 6 ++++--
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index a6e64b4ab..6a643bfcc 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -245,7 +245,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
hide_favorites: user.hide_favorites,
relationship: relationship,
skip_thread_containment: user.skip_thread_containment,
- background_image: image_url(user.background) |> MediaProxy.url()
+ background_image: image_url(user.background) |> MediaProxy.url(),
+ accepts_chat_messages: user.accepts_chat_messages
}
}
|> maybe_put_role(user, opts[:for])
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 80b1f734c..3234a26a2 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -85,7 +85,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_followers_count: false,
hide_follows_count: false,
relationship: %{},
- skip_thread_containment: false
+ skip_thread_containment: false,
+ accepts_chat_messages: false
}
}
@@ -162,7 +163,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_followers_count: false,
hide_follows_count: false,
relationship: %{},
- skip_thread_containment: false
+ skip_thread_containment: false,
+ accepts_chat_messages: false
}
}
From 37fdb05058d17abde11fd3e55ce896464c7d22e4 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 13:12:23 +0200
Subject: [PATCH 020/213] User, Migration: Change `accepts_chat_messages` to be
nullable
This is to model the ambiguous state of most users.
---
lib/pleroma/user.ex | 2 +-
.../20200703101031_add_chat_acceptance_to_users.exs | 13 +++++++++----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 79e094a79..7a684b192 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -138,7 +138,7 @@ defmodule Pleroma.User do
field(:also_known_as, {:array, :string}, default: [])
field(:inbox, :string)
field(:shared_inbox, :string)
- field(:accepts_chat_messages, :boolean, default: false)
+ field(:accepts_chat_messages, :boolean, default: nil)
embeds_one(
:notification_settings,
diff --git a/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs b/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs
index 4ae3c4201..8dfda89f1 100644
--- a/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs
+++ b/priv/repo/migrations/20200703101031_add_chat_acceptance_to_users.exs
@@ -1,12 +1,17 @@
defmodule Pleroma.Repo.Migrations.AddChatAcceptanceToUsers do
use Ecto.Migration
- def change do
+ def up do
alter table(:users) do
- add(:accepts_chat_messages, :boolean, nullable: false, default: false)
+ add(:accepts_chat_messages, :boolean, nullable: true)
end
- # Looks stupid but makes the update much faster
- execute("update users set accepts_chat_messages = local where local = true")
+ execute("update users set accepts_chat_messages = true where local = true")
+ end
+
+ def down do
+ alter table(:users) do
+ remove(:accepts_chat_messages)
+ end
end
end
From db76c26469f234ca36e9c16deb01de63055535ae Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 13:24:16 +0200
Subject: [PATCH 021/213] AccountViewTest: Fix test.
---
test/web/mastodon_api/views/account_view_test.exs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 3234a26a2..4aba6aaf1 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_follows_count: false,
relationship: %{},
skip_thread_containment: false,
- accepts_chat_messages: false
+ accepts_chat_messages: nil
}
}
@@ -164,7 +164,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
hide_follows_count: false,
relationship: %{},
skip_thread_containment: false,
- accepts_chat_messages: false
+ accepts_chat_messages: nil
}
}
From 26a7cc3f003d79d6026d67a3a8370516b13c2c90 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 13:38:59 +0200
Subject: [PATCH 022/213] UserView: Add acceptsChatMessages field
---
lib/pleroma/web/activity_pub/views/user_view.ex | 10 ++++++++++
test/web/activity_pub/views/user_view_test.exs | 12 ++++++++++++
2 files changed, 22 insertions(+)
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 4a02b09a1..d062d6230 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -81,6 +81,15 @@ defmodule Pleroma.Web.ActivityPub.UserView do
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
+ chat_message_acceptance =
+ if is_boolean(user.accepts_chat_messages) do
+ %{
+ "acceptsChatMessages" => user.accepts_chat_messages
+ }
+ else
+ %{}
+ end
+
%{
"id" => user.ap_id,
"type" => user.actor_type,
@@ -103,6 +112,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"tag" => emoji_tags,
"discoverable" => user.discoverable
}
+ |> Map.merge(chat_message_acceptance)
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|> Map.merge(Utils.make_json_ld_header())
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index bec15a996..3b4a1bcde 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -158,4 +158,16 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
end
end
+
+ describe "acceptsChatMessages" do
+ test "it returns this value if it is set" do
+ true_user = insert(:user, accepts_chat_messages: true)
+ false_user = insert(:user, accepts_chat_messages: false)
+ nil_user = insert(:user, accepts_chat_messages: nil)
+
+ assert %{"acceptsChatMessages" => true} = UserView.render("user.json", user: true_user)
+ assert %{"acceptsChatMessages" => false} = UserView.render("user.json", user: false_user)
+ refute Map.has_key?(UserView.render("user.json", user: nil_user), "acceptsChatMessages")
+ end
+ end
end
From 8289ec67a80697a1a4843c0ea50e66b01bf3bb00 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 13:39:21 +0200
Subject: [PATCH 023/213] Litepub: Add acceptsChatMessages to schema.
---
priv/static/schemas/litepub-0.1.jsonld | 1 +
1 file changed, 1 insertion(+)
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index 7cc3fee40..c1bcad0f8 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -13,6 +13,7 @@
},
"discoverable": "toot:discoverable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "acceptsChatMessages": "litepub:acceptsChatMessages",
"ostatus": "http://ostatus.org#",
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
From 5c0bf4c4721f03bd854d4466e77aa08e260c9299 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 13:58:34 +0200
Subject: [PATCH 024/213] ActivityPub: Ingest information about chat
acceptance.
---
lib/pleroma/user.ex | 3 +-
lib/pleroma/web/activity_pub/activity_pub.ex | 4 +-
.../tesla_mock/admin@mastdon.example.org.json | 1 +
test/web/activity_pub/activity_pub_test.exs | 63 ++++++++++---------
4 files changed, 41 insertions(+), 30 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 7a684b192..a4130c89f 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -437,7 +437,8 @@ defmodule Pleroma.User do
:discoverable,
:invisible,
:actor_type,
- :also_known_as
+ :also_known_as,
+ :accepts_chat_messages
]
)
|> validate_required([:name, :ap_id])
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 94117202c..86428b861 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1224,6 +1224,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end)
locked = data["manuallyApprovesFollowers"] || false
+ accepts_chat_messages = data["acceptsChatMessages"]
data = Transmogrifier.maybe_fix_user_object(data)
discoverable = data["discoverable"] || false
invisible = data["invisible"] || false
@@ -1262,7 +1263,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
also_known_as: Map.get(data, "alsoKnownAs", []),
public_key: public_key,
inbox: data["inbox"],
- shared_inbox: shared_inbox
+ shared_inbox: shared_inbox,
+ accepts_chat_messages: accepts_chat_messages
}
# nickname can be nil because of virtual actors
diff --git a/test/fixtures/tesla_mock/admin@mastdon.example.org.json b/test/fixtures/tesla_mock/admin@mastdon.example.org.json
index 9fdd6557c..f5cf174be 100644
--- a/test/fixtures/tesla_mock/admin@mastdon.example.org.json
+++ b/test/fixtures/tesla_mock/admin@mastdon.example.org.json
@@ -26,6 +26,7 @@
"summary": "\u003cp\u003e\u003c/p\u003e",
"url": "http://mastodon.example.org/@admin",
"manuallyApprovesFollowers": false,
+ "acceptsChatMessages": true,
"publicKey": {
"id": "http://mastodon.example.org/users/admin#main-key",
"owner": "http://mastodon.example.org/users/admin",
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 575e0c5db..ef69f3d91 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -184,38 +184,45 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert User.invisible?(user)
end
- test "it fetches the appropriate tag-restricted posts" do
- user = insert(:user)
+ test "it returns a user that accepts chat messages" do
+ user_id = "http://mastodon.example.org/users/admin"
+ {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
- {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"})
- {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
- {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
-
- fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
-
- fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
-
- fetch_three =
- ActivityPub.fetch_activities([], %{
- type: "Create",
- tag: ["test", "essais"],
- tag_reject: ["reject"]
- })
-
- fetch_four =
- ActivityPub.fetch_activities([], %{
- type: "Create",
- tag: ["test"],
- tag_all: ["test", "reject"]
- })
-
- assert fetch_one == [status_one, status_three]
- assert fetch_two == [status_one, status_two, status_three]
- assert fetch_three == [status_one, status_two]
- assert fetch_four == [status_three]
+ assert user.accepts_chat_messages
end
end
+ test "it fetches the appropriate tag-restricted posts" do
+ user = insert(:user)
+
+ {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"})
+ {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
+ {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"})
+
+ fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
+
+ fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["test", "essais"]})
+
+ fetch_three =
+ ActivityPub.fetch_activities([], %{
+ type: "Create",
+ tag: ["test", "essais"],
+ tag_reject: ["reject"]
+ })
+
+ fetch_four =
+ ActivityPub.fetch_activities([], %{
+ type: "Create",
+ tag: ["test"],
+ tag_all: ["test", "reject"]
+ })
+
+ assert fetch_one == [status_one, status_three]
+ assert fetch_two == [status_one, status_two, status_three]
+ assert fetch_three == [status_one, status_two]
+ assert fetch_four == [status_three]
+ end
+
describe "insertion" do
test "drops activities beyond a certain limit" do
limit = Config.get([:instance, :remote_limit])
From b374fd622b120668bb828155e32f9b4f4a142911 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 14:24:54 +0200
Subject: [PATCH 025/213] Docs: Document the added `accepts_chat_messages` user
property.
---
docs/API/differences_in_mastoapi_responses.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 72b5984ae..755db0e65 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -71,6 +71,7 @@ Has these additional fields under the `pleroma` object:
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
- `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned.
+- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
### Source
From 3ca9af1f9fef081830820b5bea90f789e460b83a Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 14:31:04 +0200
Subject: [PATCH 026/213] Account Schema: Add `accepts_chat_messages`
---
lib/pleroma/web/api_spec/schemas/account.ex | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 84f18f1b6..3a84a1593 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -102,7 +102,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
type: :object,
description:
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
- }
+ },
+ accepts_chat_messages: %Schema{type: :boolean, nullable: true}
}
},
source: %Schema{
@@ -169,6 +170,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"is_admin" => false,
"is_moderator" => false,
"skip_thread_containment" => false,
+ "accepts_chat_messages" => true,
"chat_token" =>
"SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
"unread_conversation_count" => 0,
From 4a7b89e37217af4d98746bb934b8264d7a8de51d Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 15:13:27 +0200
Subject: [PATCH 027/213] ChatMessageValidator: Additional validation.
---
.../object_validators/chat_message_validator.ex | 6 ++++++
test/web/activity_pub/object_validator_test.exs | 11 +++++++++++
2 files changed, 17 insertions(+)
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
index c481d79e0..91b475393 100644
--- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -93,12 +93,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
- If both users are in our system
- If at least one of the users in this ChatMessage is a local user
- If the recipient is not blocking the actor
+ - If the recipient is explicitly not accepting chat messages
"""
def validate_local_concern(cng) do
with actor_ap <- get_field(cng, :actor),
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
{_, %User{} = recipient} <-
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
+ {_, false} <- {:not_accepting_chats?, recipient.accepts_chat_messages == false},
{_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
cng
@@ -107,6 +109,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
cng
|> add_error(:actor, "actor is blocked by recipient")
+ {:not_accepting_chats?, true} ->
+ cng
+ |> add_error(:to, "recipient does not accept chat messages")
+
{:local?, false} ->
cng
|> add_error(:actor, "actor and recipient are both remote")
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index f38bf7e08..c1a872297 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -223,6 +223,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
end
+ test "does not validate if the recipient is not accepting chat messages", %{
+ valid_chat_message: valid_chat_message,
+ recipient: recipient
+ } do
+ recipient
+ |> Ecto.Changeset.change(%{accepts_chat_messages: false})
+ |> Pleroma.Repo.update!()
+
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
+ end
+
test "does not validate if the actor or the recipient is not in our system", %{
valid_chat_message: valid_chat_message
} do
From e3b5559780f798945eea59170afa9ef41bbf59b3 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 15:54:25 +0200
Subject: [PATCH 028/213] AccountController: Make setting accepts_chat_messages
possible.
---
lib/pleroma/user.ex | 3 ++-
lib/pleroma/web/api_spec/operations/account_operation.ex | 9 +++++++--
.../web/mastodon_api/controllers/account_controller.ex | 3 ++-
.../account_controller/update_credentials_test.exs | 7 +++++++
4 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index a4130c89f..712bc3047 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -483,7 +483,8 @@ defmodule Pleroma.User do
:pleroma_settings_store,
:discoverable,
:actor_type,
- :also_known_as
+ :also_known_as,
+ :accepts_chat_messages
]
)
|> unique_constraint(:nickname)
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 9bde8fc0d..3c05fa55f 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -61,7 +61,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
description: "Update the user's display and preferences.",
operationId: "AccountController.update_credentials",
security: [%{"oAuth" => ["write:accounts"]}],
- requestBody: request_body("Parameters", update_creadentials_request(), required: true),
+ requestBody: request_body("Parameters", update_credentials_request(), required: true),
responses: %{
200 => Operation.response("Account", "application/json", Account),
403 => Operation.response("Error", "application/json", ApiError)
@@ -458,7 +458,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
- defp update_creadentials_request do
+ defp update_credentials_request do
%Schema{
title: "AccountUpdateCredentialsRequest",
description: "POST body for creating an account",
@@ -492,6 +492,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
nullable: true,
description: "Whether manual approval of follow requests is required."
},
+ accepts_chat_messages: %Schema{
+ allOf: [BooleanLike],
+ nullable: true,
+ description: "Whether the user accepts receiving chat messages."
+ },
fields_attributes: %Schema{
nullable: true,
oneOf: [
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index b5008d69b..7ff767db6 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -160,7 +160,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:show_role,
:skip_thread_containment,
:allow_following_move,
- :discoverable
+ :discoverable,
+ :accepts_chat_messages
]
|> Enum.reduce(%{}, fn key, acc ->
Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index f67d294ba..37e33bc33 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -108,6 +108,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
assert user_data["locked"] == true
end
+ test "updates the user's chat acceptance status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{accepts_chat_messages: "false"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["pleroma"]["accepts_chat_messages"] == false
+ end
+
test "updates the user's allow_following_move", %{user: user, conn: conn} do
assert user.allow_following_move == true
From 01695716c8d8916e8a9ddc3c07edfd45c7d5c8f2 Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 15:55:18 +0200
Subject: [PATCH 029/213] Docs: Document `accepts_chat_messages` setting.
---
docs/API/differences_in_mastoapi_responses.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 755db0e65..4514a7d59 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -186,6 +186,7 @@ Additional parameters can be added to the JSON body/Form data:
- `pleroma_background_image` - sets the background image of the user.
- `discoverable` - if true, discovery of this account in search results and other services is allowed.
- `actor_type` - the type of this account.
+- `accepts_chat_messages` - if false, this account will reject all chat messages.
### Pleroma Settings Store
From ef4c16f6f19c0544ed22972c78195547b4cf3f5d Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 3 Jul 2020 15:59:42 +0200
Subject: [PATCH 030/213] Update changelog
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26f878a76..81265a7a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
+- Chats: Added `accepts_chat_messages` field to user, exposed in APIs and federation.
- Chats: Added support for federated chats. For details, see the docs.
- ActivityPub: Added support for existing AP ids for instances migrated from Mastodon.
- Instance: Add `background_image` to configuration and `/api/v1/instance`
From 4d3d867f10779bd4804acdb8ff398d41daafc4ea Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 3 Jul 2020 10:37:07 -0500
Subject: [PATCH 031/213] Update Oban to 2.0-rc3
---
mix.exs | 2 +-
mix.lock | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/mix.exs b/mix.exs
index a82e7d53b..69d9f8632 100644
--- a/mix.exs
+++ b/mix.exs
@@ -124,7 +124,7 @@ defmodule Pleroma.Mixfile do
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.4.4"},
{:postgrex, ">= 0.13.5"},
- {:oban, "~> 2.0.0-rc.2"},
+ {:oban, "~> 2.0.0-rc.3"},
{:gettext, "~> 0.15"},
{:pbkdf2_elixir, "~> 1.0"},
{:bcrypt_elixir, "~> 2.0"},
diff --git a/mix.lock b/mix.lock
index 781b7f2f2..88005451a 100644
--- a/mix.lock
+++ b/mix.lock
@@ -75,7 +75,7 @@
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
- "oban": {:hex, :oban, "2.0.0-rc.2", "4a3ba53af98a9aaeee7e53209bbdb18a80972952d4c2ccc6ac61ffd30fa96e8a", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a01ace5b6cd142fea547a554b7b752be7ea8fb08e7ffee57405d3b28561560c"},
+ "oban": {:hex, :oban, "2.0.0-rc.3", "964629fabc21939d7258a05a38f74b676bd4eebcf4932389e8ad9f1a18431bd2", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "82c9688e066610a88776aac527022a320faed9b5918093061caf2767863cc3c5"},
"open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
From bc956d0c419f156915dbf0185d6dd9c98dd7a6fe Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 3 Jul 2020 11:29:17 -0500
Subject: [PATCH 032/213] Document Oban update
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85401809a..f1c0209fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
+- Update Oban to version 2.0
API Changes
From e8710a3f8730be85443344640d2e46cd74667d6b Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 3 Jul 2020 13:49:02 -0500
Subject: [PATCH 033/213] Revert "Document Oban update"
This reverts commit bc956d0c419f156915dbf0185d6dd9c98dd7a6fe.
---
CHANGELOG.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1c0209fa..85401809a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MFR policy to set global expiration for all local Create activities
- OGP rich media parser merged with TwitterCard
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
-- Update Oban to version 2.0
API Changes
From eaa59daa4c229bf47e30ac389563c82b11378e07 Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 3 Jul 2020 17:06:20 -0500
Subject: [PATCH 034/213] Add Captcha endpoint to CSP headers when MediaProxy
is enabled.
Our CSP rules are lax when MediaProxy enabled, but lenient otherwise.
This fixes broken captcha on instances not using MediaProxy.
---
lib/pleroma/plugs/http_security_plug.ex | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 1420a9611..f7192ebfc 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -125,11 +125,19 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
+ captcha_method = Config.get([Pleroma.Captcha, :method])
+
+ captcha_endpoint =
+ if Config.get([Pleroma.Captcha, :enabled]) &&
+ captcha_method != "Pleroma.Captcha.Native",
+ do: Config.get([captcha_method, :endpoint])
+
[]
|> add_source(media_proxy_base_url)
|> add_source(upload_base_url)
|> add_source(s3_endpoint)
|> add_source(media_proxy_whitelist)
+ |> add_source(captcha_endpoint)
end
defp add_source(iodata, nil), do: iodata
From e9a28078ad969204faae600df3ddff8e75ed2f8a Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 3 Jul 2020 17:18:22 -0500
Subject: [PATCH 035/213] Rename function and clarify that CSP is only strict
with MediaProxy enabled
---
lib/pleroma/plugs/http_security_plug.ex | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index f7192ebfc..23a641faf 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -69,10 +69,11 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
img_src = "img-src 'self' data: blob:"
media_src = "media-src 'self'"
+ # Strict multimedia CSP enforcement only when MediaProxy is enabled
{img_src, media_src} =
if Config.get([:media_proxy, :enabled]) &&
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
- sources = get_proxy_and_attachment_sources()
+ sources = build_csp_multimedia_source_list()
{[img_src, sources], [media_src, sources]}
else
{[img_src, " https:"], [media_src, " https:"]}
@@ -107,7 +108,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|> :erlang.iolist_to_binary()
end
- defp get_proxy_and_attachment_sources do
+ defp build_csp_multimedia_source_list do
media_proxy_whitelist =
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->
add_source(acc, host)
From 991bd78ddad74641f8032c7b373771a5acb10da9 Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Fri, 3 Jul 2020 17:19:43 -0500
Subject: [PATCH 036/213] Document the Captcha CSP fix
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85401809a..4b74d064c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -73,6 +73,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Resolving Peertube accounts with Webfinger
- `blob:` urls not being allowed by connect-src CSP
- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
+- Fix CSP policy generation to include remote Captcha services
## [Unreleased (patch)]
From af612bd006a2792e27f9b995c0c86e010cc77e6c Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Sun, 5 Jul 2020 10:11:43 -0500
Subject: [PATCH 037/213] Ensure all CSP parameters for remote hosts have a
scheme
---
lib/pleroma/plugs/http_security_plug.ex | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 23a641faf..3bf0b8ce7 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -116,22 +116,22 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
media_proxy_base_url =
if Config.get([:media_proxy, :base_url]),
- do: URI.parse(Config.get([:media_proxy, :base_url])).host
+ do: build_csp_param(Config.get([:media_proxy, :base_url]))
upload_base_url =
if Config.get([Pleroma.Upload, :base_url]),
- do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host
+ do: build_csp_param(Config.get([Pleroma.Upload, :base_url]))
s3_endpoint =
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
- do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
+ do: build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
captcha_method = Config.get([Pleroma.Captcha, :method])
captcha_endpoint =
if Config.get([Pleroma.Captcha, :enabled]) &&
captcha_method != "Pleroma.Captcha.Native",
- do: Config.get([captcha_method, :endpoint])
+ do: build_csp_param(Config.get([captcha_method, :endpoint]))
[]
|> add_source(media_proxy_base_url)
@@ -148,6 +148,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
+ defp build_csp_param(url) when is_binary(url) do
+ %{host: host, scheme: scheme} = URI.parse(url)
+
+ if scheme do
+ scheme <> "://" <> host
+ end
+ end
+
def warn_if_disabled do
unless Config.get([:http_security, :enabled]) do
Logger.warn("
From fc1f34b85125b24a8094aaa963acb46acacd8eee Mon Sep 17 00:00:00 2001
From: Roman Chvanikov
Date: Mon, 6 Jul 2020 00:01:25 +0300
Subject: [PATCH 038/213] Delete activity before sending response to client
---
.../mastodon_api/controllers/status_controller.ex | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index 3f4c53437..12be530c9 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -201,15 +201,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
@doc "DELETE /api/v1/statuses/:id"
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
- render <-
- try_render(conn, "show.json",
- activity: activity,
- for: user,
- with_direct_conversation_id: true,
- with_source: true
- ),
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
- render
+ try_render(conn, "show.json",
+ activity: activity,
+ for: user,
+ with_direct_conversation_id: true,
+ with_source: true
+ )
else
_e -> {:error, :not_found}
end
From 208baf157ad0c8be470566d5d51d0214c229e6a5 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 11:38:40 +0200
Subject: [PATCH 039/213] ActivityPub: Add new 'capabilities' to user.
---
lib/pleroma/web/activity_pub/activity_pub.ex | 3 ++-
lib/pleroma/web/activity_pub/views/user_view.ex | 6 +++---
priv/static/schemas/litepub-0.1.jsonld | 2 +-
.../tesla_mock/admin@mastdon.example.org.json | 4 +++-
test/web/activity_pub/views/user_view_test.exs | 13 ++++++++++---
5 files changed, 19 insertions(+), 9 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 86428b861..17c9d8f21 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1224,7 +1224,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end)
locked = data["manuallyApprovesFollowers"] || false
- accepts_chat_messages = data["acceptsChatMessages"]
+ capabilities = data["capabilities"] || %{}
+ accepts_chat_messages = capabilities["acceptsChatMessages"]
data = Transmogrifier.maybe_fix_user_object(data)
discoverable = data["discoverable"] || false
invisible = data["invisible"] || false
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index d062d6230..3a4564912 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -81,7 +81,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
- chat_message_acceptance =
+ capabilities =
if is_boolean(user.accepts_chat_messages) do
%{
"acceptsChatMessages" => user.accepts_chat_messages
@@ -110,9 +110,9 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"endpoints" => endpoints,
"attachment" => fields,
"tag" => emoji_tags,
- "discoverable" => user.discoverable
+ "discoverable" => user.discoverable,
+ "capabilities" => capabilities
}
- |> Map.merge(chat_message_acceptance)
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|> Map.merge(Utils.make_json_ld_header())
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index c1bcad0f8..e7722cf72 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -13,7 +13,7 @@
},
"discoverable": "toot:discoverable",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
- "acceptsChatMessages": "litepub:acceptsChatMessages",
+ "capabilities": "litepub:capabilities",
"ostatus": "http://ostatus.org#",
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
diff --git a/test/fixtures/tesla_mock/admin@mastdon.example.org.json b/test/fixtures/tesla_mock/admin@mastdon.example.org.json
index f5cf174be..a911b979a 100644
--- a/test/fixtures/tesla_mock/admin@mastdon.example.org.json
+++ b/test/fixtures/tesla_mock/admin@mastdon.example.org.json
@@ -26,7 +26,9 @@
"summary": "\u003cp\u003e\u003c/p\u003e",
"url": "http://mastodon.example.org/@admin",
"manuallyApprovesFollowers": false,
- "acceptsChatMessages": true,
+ "capabilities": {
+ "acceptsChatMessages": true
+ },
"publicKey": {
"id": "http://mastodon.example.org/users/admin#main-key",
"owner": "http://mastodon.example.org/users/admin",
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index 3b4a1bcde..98c7c9d09 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -165,9 +165,16 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
false_user = insert(:user, accepts_chat_messages: false)
nil_user = insert(:user, accepts_chat_messages: nil)
- assert %{"acceptsChatMessages" => true} = UserView.render("user.json", user: true_user)
- assert %{"acceptsChatMessages" => false} = UserView.render("user.json", user: false_user)
- refute Map.has_key?(UserView.render("user.json", user: nil_user), "acceptsChatMessages")
+ assert %{"capabilities" => %{"acceptsChatMessages" => true}} =
+ UserView.render("user.json", user: true_user)
+
+ assert %{"capabilities" => %{"acceptsChatMessages" => false}} =
+ UserView.render("user.json", user: false_user)
+
+ refute Map.has_key?(
+ UserView.render("user.json", user: nil_user)["capabilities"],
+ "acceptsChatMessages"
+ )
end
end
end
From 0aa4c20d78b683a2d897e7f6629fb7f5d848d7ba Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 12:59:06 +0200
Subject: [PATCH 040/213] ObjectValidator Test: Extract attachments.
---
.../activity_pub/object_validator_test.exs | 67 +----------------
.../attachment_validator_test.exs | 74 +++++++++++++++++++
2 files changed, 78 insertions(+), 63 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/attachment_validator_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index f38bf7e08..361ec5526 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
use Pleroma.DataCase
@@ -5,75 +9,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
- describe "attachments" do
- test "works with honkerific attachments" do
- attachment = %{
- "mediaType" => "",
- "name" => "",
- "summary" => "298p3RG7j27tfsZ9RQ.jpg",
- "type" => "Document",
- "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
- }
-
- assert {:ok, attachment} =
- AttachmentValidator.cast_and_validate(attachment)
- |> Ecto.Changeset.apply_action(:insert)
-
- assert attachment.mediaType == "application/octet-stream"
- end
-
- test "it turns mastodon attachments into our attachments" do
- attachment = %{
- "url" =>
- "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
- "type" => "Document",
- "name" => nil,
- "mediaType" => "image/jpeg"
- }
-
- {:ok, attachment} =
- AttachmentValidator.cast_and_validate(attachment)
- |> Ecto.Changeset.apply_action(:insert)
-
- assert [
- %{
- href:
- "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
- type: "Link",
- mediaType: "image/jpeg"
- }
- ] = attachment.url
-
- assert attachment.mediaType == "image/jpeg"
- end
-
- test "it handles our own uploads" do
- user = insert(:user)
-
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- {:ok, attachment} =
- attachment.data
- |> AttachmentValidator.cast_and_validate()
- |> Ecto.Changeset.apply_action(:insert)
-
- assert attachment.mediaType == "image/jpeg"
- end
- end
-
describe "chat message create activities" do
test "it is invalid if the object already exists" do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/attachment_validator_test.exs b/test/web/activity_pub/object_validators/attachment_validator_test.exs
new file mode 100644
index 000000000..558bb3131
--- /dev/null
+++ b/test/web/activity_pub/object_validators/attachment_validator_test.exs
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+
+ import Pleroma.Factory
+
+ describe "attachments" do
+ test "works with honkerific attachments" do
+ attachment = %{
+ "mediaType" => "",
+ "name" => "",
+ "summary" => "298p3RG7j27tfsZ9RQ.jpg",
+ "type" => "Document",
+ "url" => "https://honk.tedunangst.com/d/298p3RG7j27tfsZ9RQ.jpg"
+ }
+
+ assert {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "application/octet-stream"
+ end
+
+ test "it turns mastodon attachments into our attachments" do
+ attachment = %{
+ "url" =>
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ "type" => "Document",
+ "name" => nil,
+ "mediaType" => "image/jpeg"
+ }
+
+ {:ok, attachment} =
+ AttachmentValidator.cast_and_validate(attachment)
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert [
+ %{
+ href:
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg",
+ type: "Link",
+ mediaType: "image/jpeg"
+ }
+ ] = attachment.url
+
+ assert attachment.mediaType == "image/jpeg"
+ end
+
+ test "it handles our own uploads" do
+ user = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ {:ok, attachment} =
+ attachment.data
+ |> AttachmentValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert)
+
+ assert attachment.mediaType == "image/jpeg"
+ end
+ end
+end
From e0baaa967ccf11d65a493e8e74073b1473c14a6c Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 13:01:44 +0200
Subject: [PATCH 041/213] ObjectValidator tests: Extract chat tests
---
.../activity_pub/object_validator_test.exs | 186 ----------------
.../chat_validation_test.exs | 200 ++++++++++++++++++
2 files changed, 200 insertions(+), 186 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/chat_validation_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index 361ec5526..b052c0a9e 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -15,192 +15,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
import Pleroma.Factory
- describe "chat message create activities" do
- test "it is invalid if the object already exists" do
- user = insert(:user)
- recipient = insert(:user)
- {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
- object = Object.normalize(activity, false)
-
- {:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
-
- {:error, cng} = ObjectValidator.validate(create_data, [])
-
- assert {:object, {"The object to create already exists", []}} in cng.errors
- end
-
- test "it is invalid if the object data has a different `to` or `actor` field" do
- user = insert(:user)
- recipient = insert(:user)
- {:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
-
- {:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
-
- {:error, cng} = ObjectValidator.validate(create_data, [])
-
- assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
- assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
- end
- end
-
- describe "chat messages" do
- setup do
- clear_config([:instance, :remote_limit])
- user = insert(:user)
- recipient = insert(:user, local: false)
-
- {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
-
- %{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
- end
-
- test "let's through some basic html", %{user: user, recipient: recipient} do
- {:ok, valid_chat_message, _} =
- Builder.chat_message(
- user,
- recipient.ap_id,
- "hey example "
- )
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["content"] ==
- "hey example alert('uguu')"
- end
-
- test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert Map.put(valid_chat_message, "attachment", nil) == object
- end
-
- test "validates for a basic object with an attachment", %{
- valid_chat_message: valid_chat_message,
- user: user
- } do
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- valid_chat_message =
- valid_chat_message
- |> Map.put("attachment", attachment.data)
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["attachment"]
- end
-
- test "validates for a basic object with an attachment in an array", %{
- valid_chat_message: valid_chat_message,
- user: user
- } do
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- valid_chat_message =
- valid_chat_message
- |> Map.put("attachment", [attachment.data])
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["attachment"]
- end
-
- test "validates for a basic object with an attachment but without content", %{
- valid_chat_message: valid_chat_message,
- user: user
- } do
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
-
- valid_chat_message =
- valid_chat_message
- |> Map.put("attachment", attachment.data)
- |> Map.delete("content")
-
- assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
-
- assert object["attachment"]
- end
-
- test "does not validate if the message has no content", %{
- valid_chat_message: valid_chat_message
- } do
- contentless =
- valid_chat_message
- |> Map.delete("content")
-
- refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
- end
-
- test "does not validate if the message is longer than the remote_limit", %{
- valid_chat_message: valid_chat_message
- } do
- Pleroma.Config.put([:instance, :remote_limit], 2)
- refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
- end
-
- test "does not validate if the recipient is blocking the actor", %{
- valid_chat_message: valid_chat_message,
- user: user,
- recipient: recipient
- } do
- Pleroma.User.block(recipient, user)
- refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
- end
-
- test "does not validate if the actor or the recipient is not in our system", %{
- valid_chat_message: valid_chat_message
- } do
- chat_message =
- valid_chat_message
- |> Map.put("actor", "https://raymoo.com/raymoo")
-
- {:error, _} = ObjectValidator.validate(chat_message, [])
-
- chat_message =
- valid_chat_message
- |> Map.put("to", ["https://raymoo.com/raymoo"])
-
- {:error, _} = ObjectValidator.validate(chat_message, [])
- end
-
- test "does not validate for a message with multiple recipients", %{
- valid_chat_message: valid_chat_message,
- user: user,
- recipient: recipient
- } do
- chat_message =
- valid_chat_message
- |> Map.put("to", [user.ap_id, recipient.ap_id])
-
- assert {:error, _} = ObjectValidator.validate(chat_message, [])
- end
-
- test "does not validate if it doesn't concern local users" do
- user = insert(:user, local: false)
- recipient = insert(:user, local: false)
-
- {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
- assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
- end
- end
-
describe "EmojiReacts" do
setup do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/chat_validation_test.exs b/test/web/activity_pub/object_validators/chat_validation_test.exs
new file mode 100644
index 000000000..ec1e497fa
--- /dev/null
+++ b/test/web/activity_pub/object_validators/chat_validation_test.exs
@@ -0,0 +1,200 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatValidationTest do
+ use Pleroma.DataCase
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "chat message create activities" do
+ test "it is invalid if the object already exists" do
+ user = insert(:user)
+ recipient = insert(:user)
+ {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "hey")
+ object = Object.normalize(activity, false)
+
+ {:ok, create_data, _} = Builder.create(user, object.data, [recipient.ap_id])
+
+ {:error, cng} = ObjectValidator.validate(create_data, [])
+
+ assert {:object, {"The object to create already exists", []}} in cng.errors
+ end
+
+ test "it is invalid if the object data has a different `to` or `actor` field" do
+ user = insert(:user)
+ recipient = insert(:user)
+ {:ok, object_data, _} = Builder.chat_message(recipient, user.ap_id, "Hey")
+
+ {:ok, create_data, _} = Builder.create(user, object_data, [recipient.ap_id])
+
+ {:error, cng} = ObjectValidator.validate(create_data, [])
+
+ assert {:to, {"Recipients don't match with object recipients", []}} in cng.errors
+ assert {:actor, {"Actor doesn't match with object actor", []}} in cng.errors
+ end
+ end
+
+ describe "chat messages" do
+ setup do
+ clear_config([:instance, :remote_limit])
+ user = insert(:user)
+ recipient = insert(:user, local: false)
+
+ {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey :firefox:")
+
+ %{user: user, recipient: recipient, valid_chat_message: valid_chat_message}
+ end
+
+ test "let's through some basic html", %{user: user, recipient: recipient} do
+ {:ok, valid_chat_message, _} =
+ Builder.chat_message(
+ user,
+ recipient.ap_id,
+ "hey example "
+ )
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["content"] ==
+ "hey example alert('uguu')"
+ end
+
+ test "validates for a basic object we build", %{valid_chat_message: valid_chat_message} do
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert Map.put(valid_chat_message, "attachment", nil) == object
+ end
+
+ test "validates for a basic object with an attachment", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", attachment.data)
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "validates for a basic object with an attachment in an array", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", [attachment.data])
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "validates for a basic object with an attachment but without content", %{
+ valid_chat_message: valid_chat_message,
+ user: user
+ } do
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, attachment} = ActivityPub.upload(file, actor: user.ap_id)
+
+ valid_chat_message =
+ valid_chat_message
+ |> Map.put("attachment", attachment.data)
+ |> Map.delete("content")
+
+ assert {:ok, object, _meta} = ObjectValidator.validate(valid_chat_message, [])
+
+ assert object["attachment"]
+ end
+
+ test "does not validate if the message has no content", %{
+ valid_chat_message: valid_chat_message
+ } do
+ contentless =
+ valid_chat_message
+ |> Map.delete("content")
+
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(contentless, []))
+ end
+
+ test "does not validate if the message is longer than the remote_limit", %{
+ valid_chat_message: valid_chat_message
+ } do
+ Pleroma.Config.put([:instance, :remote_limit], 2)
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
+ end
+
+ test "does not validate if the recipient is blocking the actor", %{
+ valid_chat_message: valid_chat_message,
+ user: user,
+ recipient: recipient
+ } do
+ Pleroma.User.block(recipient, user)
+ refute match?({:ok, _object, _meta}, ObjectValidator.validate(valid_chat_message, []))
+ end
+
+ test "does not validate if the actor or the recipient is not in our system", %{
+ valid_chat_message: valid_chat_message
+ } do
+ chat_message =
+ valid_chat_message
+ |> Map.put("actor", "https://raymoo.com/raymoo")
+
+ {:error, _} = ObjectValidator.validate(chat_message, [])
+
+ chat_message =
+ valid_chat_message
+ |> Map.put("to", ["https://raymoo.com/raymoo"])
+
+ {:error, _} = ObjectValidator.validate(chat_message, [])
+ end
+
+ test "does not validate for a message with multiple recipients", %{
+ valid_chat_message: valid_chat_message,
+ user: user,
+ recipient: recipient
+ } do
+ chat_message =
+ valid_chat_message
+ |> Map.put("to", [user.ap_id, recipient.ap_id])
+
+ assert {:error, _} = ObjectValidator.validate(chat_message, [])
+ end
+
+ test "does not validate if it doesn't concern local users" do
+ user = insert(:user, local: false)
+ recipient = insert(:user, local: false)
+
+ {:ok, valid_chat_message, _} = Builder.chat_message(user, recipient.ap_id, "hey")
+ assert {:error, _} = ObjectValidator.validate(valid_chat_message, [])
+ end
+ end
+end
From 60d4c6c91d390633a6e765b51295e8b300ee9efe Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:02:35 +0200
Subject: [PATCH 042/213] ObjectValidator tests: Extract emoji react testing
---
.../activity_pub/object_validator_test.exs | 41 --------------
.../emoji_react_validation_test.exs | 53 +++++++++++++++++++
2 files changed, 53 insertions(+), 41 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/emoji_react_validation_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index b052c0a9e..4a8e1a0fb 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
use Pleroma.DataCase
alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
@@ -15,46 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
import Pleroma.Factory
- describe "EmojiReacts" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
-
- object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
-
- {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
-
- %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
- end
-
- test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
- assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
- end
-
- test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
- without_content =
- valid_emoji_react
- |> Map.delete("content")
-
- {:error, cng} = ObjectValidator.validate(without_content, [])
-
- refute cng.valid?
- assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
- end
-
- test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
- without_emoji_content =
- valid_emoji_react
- |> Map.put("content", "x")
-
- {:error, cng} = ObjectValidator.validate(without_emoji_content, [])
-
- refute cng.valid?
-
- assert {:content, {"must be a single character emoji", []}} in cng.errors
- end
- end
-
describe "Undos" do
setup do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/emoji_react_validation_test.exs b/test/web/activity_pub/object_validators/emoji_react_validation_test.exs
new file mode 100644
index 000000000..582e6d785
--- /dev/null
+++ b/test/web/activity_pub/object_validators/emoji_react_validation_test.exs
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "EmojiReacts" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+
+ object = Pleroma.Object.get_by_ap_id(post_activity.data["object"])
+
+ {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌")
+
+ %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react}
+ end
+
+ test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do
+ assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, [])
+ end
+
+ test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do
+ without_content =
+ valid_emoji_react
+ |> Map.delete("content")
+
+ {:error, cng} = ObjectValidator.validate(without_content, [])
+
+ refute cng.valid?
+ assert {:content, {"can't be blank", [validation: :required]}} in cng.errors
+ end
+
+ test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do
+ without_emoji_content =
+ valid_emoji_react
+ |> Map.put("content", "x")
+
+ {:error, cng} = ObjectValidator.validate(without_emoji_content, [])
+
+ refute cng.valid?
+
+ assert {:content, {"must be a single character emoji", []}} in cng.errors
+ end
+ end
+end
From e6a13d97d0f9715980a12a2b73d961aefc52289a Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:05:02 +0200
Subject: [PATCH 043/213] ObjectValidation tests: Extract delete validation
tests.
---
.../activity_pub/object_validator_test.exs | 92 ---------------
.../delete_validation_test.exs | 106 ++++++++++++++++++
2 files changed, 106 insertions(+), 92 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/delete_validation_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index 4a8e1a0fb..699cb8bf8 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -54,98 +54,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
end
end
- describe "deletes" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "cancel me daddy"})
-
- {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"])
- {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id)
-
- %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete}
- end
-
- test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do
- {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, [])
-
- assert valid_post_delete["deleted_activity_id"]
- end
-
- test "it is invalid if the object isn't in a list of certain types", %{
- valid_post_delete: valid_post_delete
- } do
- object = Object.get_by_ap_id(valid_post_delete["object"])
-
- data =
- object.data
- |> Map.put("type", "Like")
-
- {:ok, _object} =
- object
- |> Ecto.Changeset.change(%{data: data})
- |> Object.update_and_set_cache()
-
- {:error, cng} = ObjectValidator.validate(valid_post_delete, [])
- assert {:object, {"object not in allowed types", []}} in cng.errors
- end
-
- test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do
- assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, []))
- end
-
- test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do
- no_id =
- valid_post_delete
- |> Map.delete("id")
-
- {:error, cng} = ObjectValidator.validate(no_id, [])
-
- assert {:id, {"can't be blank", [validation: :required]}} in cng.errors
- end
-
- test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do
- missing_object =
- valid_post_delete
- |> Map.put("object", "http://does.not/exist")
-
- {:error, cng} = ObjectValidator.validate(missing_object, [])
-
- assert {:object, {"can't find object", []}} in cng.errors
- end
-
- test "it's invalid if the actor of the object and the actor of delete are from different domains",
- %{valid_post_delete: valid_post_delete} do
- valid_user = insert(:user)
-
- valid_other_actor =
- valid_post_delete
- |> Map.put("actor", valid_user.ap_id)
-
- assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, []))
-
- invalid_other_actor =
- valid_post_delete
- |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
-
- {:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
-
- assert {:actor, {"is not allowed to delete object", []}} in cng.errors
- end
-
- test "it's valid if the actor of the object is a local superuser",
- %{valid_post_delete: valid_post_delete} do
- user =
- insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo")
-
- valid_other_actor =
- valid_post_delete
- |> Map.put("actor", user.ap_id)
-
- {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, [])
- assert meta[:do_not_federate]
- end
- end
-
describe "likes" do
setup do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/delete_validation_test.exs b/test/web/activity_pub/object_validators/delete_validation_test.exs
new file mode 100644
index 000000000..42cd18298
--- /dev/null
+++ b/test/web/activity_pub/object_validators/delete_validation_test.exs
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "deletes" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "cancel me daddy"})
+
+ {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"])
+ {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id)
+
+ %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete}
+ end
+
+ test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do
+ {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, [])
+
+ assert valid_post_delete["deleted_activity_id"]
+ end
+
+ test "it is invalid if the object isn't in a list of certain types", %{
+ valid_post_delete: valid_post_delete
+ } do
+ object = Object.get_by_ap_id(valid_post_delete["object"])
+
+ data =
+ object.data
+ |> Map.put("type", "Like")
+
+ {:ok, _object} =
+ object
+ |> Ecto.Changeset.change(%{data: data})
+ |> Object.update_and_set_cache()
+
+ {:error, cng} = ObjectValidator.validate(valid_post_delete, [])
+ assert {:object, {"object not in allowed types", []}} in cng.errors
+ end
+
+ test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do
+ assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, []))
+ end
+
+ test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do
+ no_id =
+ valid_post_delete
+ |> Map.delete("id")
+
+ {:error, cng} = ObjectValidator.validate(no_id, [])
+
+ assert {:id, {"can't be blank", [validation: :required]}} in cng.errors
+ end
+
+ test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do
+ missing_object =
+ valid_post_delete
+ |> Map.put("object", "http://does.not/exist")
+
+ {:error, cng} = ObjectValidator.validate(missing_object, [])
+
+ assert {:object, {"can't find object", []}} in cng.errors
+ end
+
+ test "it's invalid if the actor of the object and the actor of delete are from different domains",
+ %{valid_post_delete: valid_post_delete} do
+ valid_user = insert(:user)
+
+ valid_other_actor =
+ valid_post_delete
+ |> Map.put("actor", valid_user.ap_id)
+
+ assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, []))
+
+ invalid_other_actor =
+ valid_post_delete
+ |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
+
+ {:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
+
+ assert {:actor, {"is not allowed to delete object", []}} in cng.errors
+ end
+
+ test "it's valid if the actor of the object is a local superuser",
+ %{valid_post_delete: valid_post_delete} do
+ user =
+ insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo")
+
+ valid_other_actor =
+ valid_post_delete
+ |> Map.put("actor", user.ap_id)
+
+ {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, [])
+ assert meta[:do_not_federate]
+ end
+ end
+end
From 168256dce98d22179e0efc27659dbcb7b61a5fdf Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:08:11 +0200
Subject: [PATCH 044/213] ObjectValidation tests: Extract like validation
tests.
---
.../activity_pub/object_validator_test.exs | 101 ----------------
.../like_validation_test.exs | 113 ++++++++++++++++++
2 files changed, 113 insertions(+), 101 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/like_validation_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index 699cb8bf8..2b5d6e9fe 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -8,8 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
- alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
@@ -54,105 +52,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
end
end
- describe "likes" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
-
- valid_like = %{
- "to" => [user.ap_id],
- "cc" => [],
- "type" => "Like",
- "id" => Utils.generate_activity_id(),
- "object" => post_activity.data["object"],
- "actor" => user.ap_id,
- "context" => "a context"
- }
-
- %{valid_like: valid_like, user: user, post_activity: post_activity}
- end
-
- test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
- {:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
-
- assert "id" in Map.keys(object)
- end
-
- test "is valid for a valid object", %{valid_like: valid_like} do
- assert LikeValidator.cast_and_validate(valid_like).valid?
- end
-
- test "sets the 'to' field to the object actor if no recipients are given", %{
- valid_like: valid_like,
- user: user
- } do
- without_recipients =
- valid_like
- |> Map.delete("to")
-
- {:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
-
- assert object["to"] == [user.ap_id]
- end
-
- test "sets the context field to the context of the object if no context is given", %{
- valid_like: valid_like,
- post_activity: post_activity
- } do
- without_context =
- valid_like
- |> Map.delete("context")
-
- {:ok, object, _meta} = ObjectValidator.validate(without_context, [])
-
- assert object["context"] == post_activity.data["context"]
- end
-
- test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
- without_actor = Map.delete(valid_like, "actor")
-
- refute LikeValidator.cast_and_validate(without_actor).valid?
-
- with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
-
- refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
- end
-
- test "it errors when the object is missing or not known", %{valid_like: valid_like} do
- without_object = Map.delete(valid_like, "object")
-
- refute LikeValidator.cast_and_validate(without_object).valid?
-
- with_invalid_object = Map.put(valid_like, "object", "invalidobject")
-
- refute LikeValidator.cast_and_validate(with_invalid_object).valid?
- end
-
- test "it errors when the actor has already like the object", %{
- valid_like: valid_like,
- user: user,
- post_activity: post_activity
- } do
- _like = CommonAPI.favorite(user, post_activity.id)
-
- refute LikeValidator.cast_and_validate(valid_like).valid?
- end
-
- test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
- wrapped_like =
- valid_like
- |> Map.put("actor", %{"id" => valid_like["actor"]})
- |> Map.put("object", %{"id" => valid_like["object"]})
-
- validated = LikeValidator.cast_and_validate(wrapped_like)
-
- assert validated.valid?
-
- assert {:actor, valid_like["actor"]} in validated.changes
- assert {:object, valid_like["object"]} in validated.changes
- end
- end
-
describe "announces" do
setup do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/like_validation_test.exs b/test/web/activity_pub/object_validators/like_validation_test.exs
new file mode 100644
index 000000000..2c033b7e2
--- /dev/null
+++ b/test/web/activity_pub/object_validators/like_validation_test.exs
@@ -0,0 +1,113 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
+ alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "likes" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+
+ valid_like = %{
+ "to" => [user.ap_id],
+ "cc" => [],
+ "type" => "Like",
+ "id" => Utils.generate_activity_id(),
+ "object" => post_activity.data["object"],
+ "actor" => user.ap_id,
+ "context" => "a context"
+ }
+
+ %{valid_like: valid_like, user: user, post_activity: post_activity}
+ end
+
+ test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
+ {:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
+
+ assert "id" in Map.keys(object)
+ end
+
+ test "is valid for a valid object", %{valid_like: valid_like} do
+ assert LikeValidator.cast_and_validate(valid_like).valid?
+ end
+
+ test "sets the 'to' field to the object actor if no recipients are given", %{
+ valid_like: valid_like,
+ user: user
+ } do
+ without_recipients =
+ valid_like
+ |> Map.delete("to")
+
+ {:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
+
+ assert object["to"] == [user.ap_id]
+ end
+
+ test "sets the context field to the context of the object if no context is given", %{
+ valid_like: valid_like,
+ post_activity: post_activity
+ } do
+ without_context =
+ valid_like
+ |> Map.delete("context")
+
+ {:ok, object, _meta} = ObjectValidator.validate(without_context, [])
+
+ assert object["context"] == post_activity.data["context"]
+ end
+
+ test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
+ without_actor = Map.delete(valid_like, "actor")
+
+ refute LikeValidator.cast_and_validate(without_actor).valid?
+
+ with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
+
+ refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
+ end
+
+ test "it errors when the object is missing or not known", %{valid_like: valid_like} do
+ without_object = Map.delete(valid_like, "object")
+
+ refute LikeValidator.cast_and_validate(without_object).valid?
+
+ with_invalid_object = Map.put(valid_like, "object", "invalidobject")
+
+ refute LikeValidator.cast_and_validate(with_invalid_object).valid?
+ end
+
+ test "it errors when the actor has already like the object", %{
+ valid_like: valid_like,
+ user: user,
+ post_activity: post_activity
+ } do
+ _like = CommonAPI.favorite(user, post_activity.id)
+
+ refute LikeValidator.cast_and_validate(valid_like).valid?
+ end
+
+ test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
+ wrapped_like =
+ valid_like
+ |> Map.put("actor", %{"id" => valid_like["actor"]})
+ |> Map.put("object", %{"id" => valid_like["object"]})
+
+ validated = LikeValidator.cast_and_validate(wrapped_like)
+
+ assert validated.valid?
+
+ assert {:actor, valid_like["actor"]} in validated.changes
+ assert {:object, valid_like["object"]} in validated.changes
+ end
+ end
+end
From bbaf108aee28b038675e3b782c12307763624b2b Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:09:41 +0200
Subject: [PATCH 045/213] ObjectValidator tests: Extract undo validation tests.
---
.../activity_pub/object_validator_test.exs | 40 --------------
.../undo_validation_test.exs | 53 +++++++++++++++++++
2 files changed, 53 insertions(+), 40 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/undo_validation_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index 2b5d6e9fe..d41d9d73e 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -12,46 +12,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
import Pleroma.Factory
- describe "Undos" do
- setup do
- user = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
- {:ok, like} = CommonAPI.favorite(user, post_activity.id)
- {:ok, valid_like_undo, []} = Builder.undo(user, like)
-
- %{user: user, like: like, valid_like_undo: valid_like_undo}
- end
-
- test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
- assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
- end
-
- test "it does not validate if the actor of the undo is not the actor of the object", %{
- valid_like_undo: valid_like_undo
- } do
- other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
-
- bad_actor =
- valid_like_undo
- |> Map.put("actor", other_user.ap_id)
-
- {:error, cng} = ObjectValidator.validate(bad_actor, [])
-
- assert {:actor, {"not the same as object actor", []}} in cng.errors
- end
-
- test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
- missing_object =
- valid_like_undo
- |> Map.put("object", "https://gensokyo.2hu/objects/1")
-
- {:error, cng} = ObjectValidator.validate(missing_object, [])
-
- assert {:object, {"can't find object", []}} in cng.errors
- assert length(cng.errors) == 1
- end
- end
-
describe "announces" do
setup do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/undo_validation_test.exs b/test/web/activity_pub/object_validators/undo_validation_test.exs
new file mode 100644
index 000000000..75bbcc4b6
--- /dev/null
+++ b/test/web/activity_pub/object_validators/undo_validation_test.exs
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "Undos" do
+ setup do
+ user = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+ {:ok, like} = CommonAPI.favorite(user, post_activity.id)
+ {:ok, valid_like_undo, []} = Builder.undo(user, like)
+
+ %{user: user, like: like, valid_like_undo: valid_like_undo}
+ end
+
+ test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do
+ assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, [])
+ end
+
+ test "it does not validate if the actor of the undo is not the actor of the object", %{
+ valid_like_undo: valid_like_undo
+ } do
+ other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
+
+ bad_actor =
+ valid_like_undo
+ |> Map.put("actor", other_user.ap_id)
+
+ {:error, cng} = ObjectValidator.validate(bad_actor, [])
+
+ assert {:actor, {"not the same as object actor", []}} in cng.errors
+ end
+
+ test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do
+ missing_object =
+ valid_like_undo
+ |> Map.put("object", "https://gensokyo.2hu/objects/1")
+
+ {:error, cng} = ObjectValidator.validate(missing_object, [])
+
+ assert {:object, {"can't find object", []}} in cng.errors
+ assert length(cng.errors) == 1
+ end
+ end
+end
From b2e1ea9226a8f84cc83c33311a71f941c11c0d68 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:11:49 +0200
Subject: [PATCH 046/213] ObjectValidation tests: Extract announce validation
tests.
---
.../activity_pub/object_validator_test.exs | 94 ----------------
.../announce_validation_test.exs | 106 ++++++++++++++++++
2 files changed, 106 insertions(+), 94 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/announce_validation_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index d41d9d73e..cb365e409 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -5,105 +5,11 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
use Pleroma.DataCase
- alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator
- alias Pleroma.Web.CommonAPI
import Pleroma.Factory
- describe "announces" do
- setup do
- user = insert(:user)
- announcer = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
-
- object = Object.normalize(post_activity, false)
- {:ok, valid_announce, []} = Builder.announce(announcer, object)
-
- %{
- valid_announce: valid_announce,
- user: user,
- post_activity: post_activity,
- announcer: announcer
- }
- end
-
- test "returns ok for a valid announce", %{valid_announce: valid_announce} do
- assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
- end
-
- test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
- without_object =
- valid_announce
- |> Map.delete("object")
-
- {:error, cng} = ObjectValidator.validate(without_object, [])
-
- assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
-
- nonexisting_object =
- valid_announce
- |> Map.put("object", "https://gensokyo.2hu/objects/99999999")
-
- {:error, cng} = ObjectValidator.validate(nonexisting_object, [])
-
- assert {:object, {"can't find object", []}} in cng.errors
- end
-
- test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
- nonexisting_actor =
- valid_announce
- |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
-
- {:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
-
- assert {:actor, {"can't find user", []}} in cng.errors
- end
-
- test "returns an error if the actor already announced the object", %{
- valid_announce: valid_announce,
- announcer: announcer,
- post_activity: post_activity
- } do
- _announce = CommonAPI.repeat(post_activity.id, announcer)
-
- {:error, cng} = ObjectValidator.validate(valid_announce, [])
-
- assert {:actor, {"already announced this object", []}} in cng.errors
- assert {:object, {"already announced by this actor", []}} in cng.errors
- end
-
- test "returns an error if the actor can't announce the object", %{
- announcer: announcer,
- user: user
- } do
- {:ok, post_activity} =
- CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
-
- object = Object.normalize(post_activity, false)
-
- # Another user can't announce it
- {:ok, announce, []} = Builder.announce(announcer, object, public: false)
-
- {:error, cng} = ObjectValidator.validate(announce, [])
-
- assert {:actor, {"can not announce this object", []}} in cng.errors
-
- # The actor of the object can announce it
- {:ok, announce, []} = Builder.announce(user, object, public: false)
-
- assert {:ok, _, _} = ObjectValidator.validate(announce, [])
-
- # The actor of the object can not announce it publicly
- {:ok, announce, []} = Builder.announce(user, object, public: true)
-
- {:error, cng} = ObjectValidator.validate(announce, [])
-
- assert {:actor, {"can not announce this object publicly", []}} in cng.errors
- end
- end
-
describe "updates" do
setup do
user = insert(:user)
diff --git a/test/web/activity_pub/object_validators/announce_validation_test.exs b/test/web/activity_pub/object_validators/announce_validation_test.exs
new file mode 100644
index 000000000..623342f76
--- /dev/null
+++ b/test/web/activity_pub/object_validators/announce_validation_test.exs
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnouncValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "announces" do
+ setup do
+ user = insert(:user)
+ announcer = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+
+ object = Object.normalize(post_activity, false)
+ {:ok, valid_announce, []} = Builder.announce(announcer, object)
+
+ %{
+ valid_announce: valid_announce,
+ user: user,
+ post_activity: post_activity,
+ announcer: announcer
+ }
+ end
+
+ test "returns ok for a valid announce", %{valid_announce: valid_announce} do
+ assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
+ end
+
+ test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
+ without_object =
+ valid_announce
+ |> Map.delete("object")
+
+ {:error, cng} = ObjectValidator.validate(without_object, [])
+
+ assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
+
+ nonexisting_object =
+ valid_announce
+ |> Map.put("object", "https://gensokyo.2hu/objects/99999999")
+
+ {:error, cng} = ObjectValidator.validate(nonexisting_object, [])
+
+ assert {:object, {"can't find object", []}} in cng.errors
+ end
+
+ test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
+ nonexisting_actor =
+ valid_announce
+ |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
+
+ {:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
+
+ assert {:actor, {"can't find user", []}} in cng.errors
+ end
+
+ test "returns an error if the actor already announced the object", %{
+ valid_announce: valid_announce,
+ announcer: announcer,
+ post_activity: post_activity
+ } do
+ _announce = CommonAPI.repeat(post_activity.id, announcer)
+
+ {:error, cng} = ObjectValidator.validate(valid_announce, [])
+
+ assert {:actor, {"already announced this object", []}} in cng.errors
+ assert {:object, {"already announced by this actor", []}} in cng.errors
+ end
+
+ test "returns an error if the actor can't announce the object", %{
+ announcer: announcer,
+ user: user
+ } do
+ {:ok, post_activity} =
+ CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
+
+ object = Object.normalize(post_activity, false)
+
+ # Another user can't announce it
+ {:ok, announce, []} = Builder.announce(announcer, object, public: false)
+
+ {:error, cng} = ObjectValidator.validate(announce, [])
+
+ assert {:actor, {"can not announce this object", []}} in cng.errors
+
+ # The actor of the object can announce it
+ {:ok, announce, []} = Builder.announce(user, object, public: false)
+
+ assert {:ok, _, _} = ObjectValidator.validate(announce, [])
+
+ # The actor of the object can not announce it publicly
+ {:ok, announce, []} = Builder.announce(user, object, public: true)
+
+ {:error, cng} = ObjectValidator.validate(announce, [])
+
+ assert {:actor, {"can not announce this object publicly", []}} in cng.errors
+ end
+ end
+end
From 410c1fab312194bde83c9cf1c3e741875110a9ad Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:13:11 +0200
Subject: [PATCH 047/213] ObjectValidator tests: Extract update validation
tests.
---
.../activity_pub/object_validator_test.exs | 32 --------------
.../update_validation_test.exs | 44 +++++++++++++++++++
2 files changed, 44 insertions(+), 32 deletions(-)
create mode 100644 test/web/activity_pub/object_validators/update_validation_test.exs
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs
index cb365e409..ba24a5a1c 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validator_test.exs
@@ -10,38 +10,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
import Pleroma.Factory
- describe "updates" do
- setup do
- user = insert(:user)
-
- object = %{
- "id" => user.ap_id,
- "name" => "A new name",
- "summary" => "A new bio"
- }
-
- {:ok, valid_update, []} = Builder.update(user, object)
-
- %{user: user, valid_update: valid_update}
- end
-
- test "validates a basic object", %{valid_update: valid_update} do
- assert {:ok, _update, []} = ObjectValidator.validate(valid_update, [])
- end
-
- test "returns an error if the object can't be updated by the actor", %{
- valid_update: valid_update
- } do
- other_user = insert(:user)
-
- update =
- valid_update
- |> Map.put("actor", other_user.ap_id)
-
- assert {:error, _cng} = ObjectValidator.validate(update, [])
- end
- end
-
describe "blocks" do
setup do
user = insert(:user, local: false)
diff --git a/test/web/activity_pub/object_validators/update_validation_test.exs b/test/web/activity_pub/object_validators/update_validation_test.exs
new file mode 100644
index 000000000..5e80cf731
--- /dev/null
+++ b/test/web/activity_pub/object_validators/update_validation_test.exs
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateHandlingTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+
+ import Pleroma.Factory
+
+ describe "updates" do
+ setup do
+ user = insert(:user)
+
+ object = %{
+ "id" => user.ap_id,
+ "name" => "A new name",
+ "summary" => "A new bio"
+ }
+
+ {:ok, valid_update, []} = Builder.update(user, object)
+
+ %{user: user, valid_update: valid_update}
+ end
+
+ test "validates a basic object", %{valid_update: valid_update} do
+ assert {:ok, _update, []} = ObjectValidator.validate(valid_update, [])
+ end
+
+ test "returns an error if the object can't be updated by the actor", %{
+ valid_update: valid_update
+ } do
+ other_user = insert(:user)
+
+ update =
+ valid_update
+ |> Map.put("actor", other_user.ap_id)
+
+ assert {:error, _cng} = ObjectValidator.validate(update, [])
+ end
+ end
+end
From eb87430803592e6dc0b6293489ea9bde303ac534 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:14:58 +0200
Subject: [PATCH 048/213] ObjectValidator tests: Extract block validation
tests.
---
.../block_handling_test.exs} | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename test/web/activity_pub/{object_validator_test.exs => object_validators/block_handling_test.exs} (93%)
diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validators/block_handling_test.exs
similarity index 93%
rename from test/web/activity_pub/object_validator_test.exs
rename to test/web/activity_pub/object_validators/block_handling_test.exs
index ba24a5a1c..8860f4abe 100644
--- a/test/web/activity_pub/object_validator_test.exs
+++ b/test/web/activity_pub/object_validators/block_handling_test.exs
@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockHandlingTest do
use Pleroma.DataCase
alias Pleroma.Web.ActivityPub.Builder
From 4e3b3998ad849943fbae3590ce7e892d2dfc54d3 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:44:35 +0200
Subject: [PATCH 049/213] BlockValidation test: Rename.
---
.../{block_handling_test.exs => block_validation_test.exs} | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename test/web/activity_pub/object_validators/{block_handling_test.exs => block_validation_test.exs} (92%)
diff --git a/test/web/activity_pub/object_validators/block_handling_test.exs b/test/web/activity_pub/object_validators/block_validation_test.exs
similarity index 92%
rename from test/web/activity_pub/object_validators/block_handling_test.exs
rename to test/web/activity_pub/object_validators/block_validation_test.exs
index 8860f4abe..c08d4b2e8 100644
--- a/test/web/activity_pub/object_validators/block_handling_test.exs
+++ b/test/web/activity_pub/object_validators/block_validation_test.exs
@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockHandlingTest do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidationTest do
use Pleroma.DataCase
alias Pleroma.Web.ActivityPub.Builder
From a6a12b241fbacd3ff35cd901190e62d14aaac3c2 Mon Sep 17 00:00:00 2001
From: lain
Date: Mon, 6 Jul 2020 15:57:19 +0200
Subject: [PATCH 050/213] FollowValidator: Add basic validation.
---
lib/pleroma/web/activity_pub/builder.ex | 13 ++++++
.../web/activity_pub/object_validator.ex | 11 +++++
.../object_validators/follow_validator.ex | 42 +++++++++++++++++++
.../follow_validation_test.exs | 26 ++++++++++++
4 files changed, 92 insertions(+)
create mode 100644 lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
create mode 100644 test/web/activity_pub/object_validators/follow_validation_test.exs
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index cabc28de9..d5f3610ed 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
require Pleroma.Constants
+ @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
+ def follow(follower, followed) do
+ data = %{
+ "id" => Utils.generate_activity_id(),
+ "actor" => follower.ap_id,
+ "type" => "Follow",
+ "object" => followed.ap_id,
+ "to" => [followed.ap_id]
+ }
+
+ {:ok, data, []}
+ end
+
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
def emoji_react(actor, object, emoji) do
with {:ok, data, meta} <- object_action(actor, object) do
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index bb6324460..df926829c 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@@ -25,6 +26,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
def validate(object, meta)
+ def validate(%{"type" => "Follow"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> FollowValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
def validate(%{"type" => "Block"} = block_activity, meta) do
with {:ok, block_activity} <-
block_activity
diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
new file mode 100644
index 000000000..2035ad9ba
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+
+ import Ecto.Changeset
+ import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+
+ @primary_key false
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:type, :string)
+ field(:actor, ObjectValidators.ObjectID)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:object, ObjectValidators.ObjectID)
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> cast(data, __schema__(:fields))
+ end
+
+ def validate_data(cng) do
+ cng
+ |> validate_required([:id, :type, :actor, :to, :cc, :object])
+ |> validate_inclusion(:type, ["Follow"])
+ |> validate_actor_presence()
+ |> validate_actor_presence(field_name: :object)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data
+ |> validate_data
+ end
+end
diff --git a/test/web/activity_pub/object_validators/follow_validation_test.exs b/test/web/activity_pub/object_validators/follow_validation_test.exs
new file mode 100644
index 000000000..6e1378be2
--- /dev/null
+++ b/test/web/activity_pub/object_validators/follow_validation_test.exs
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+
+ import Pleroma.Factory
+
+ describe "Follows" do
+ setup do
+ follower = insert(:user)
+ followed = insert(:user)
+
+ {:ok, valid_follow, []} = Builder.follow(follower, followed)
+ %{follower: follower, followed: followed, valid_follow: valid_follow}
+ end
+
+ test "validates a basic follow object", %{valid_follow: valid_follow} do
+ assert {:ok, _follow, []} = ObjectValidator.validate(valid_follow, [])
+ end
+ end
+end
From 65843d92c4d3999f727d00500dcf8943cfe7bbc0 Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Mon, 6 Jul 2020 10:59:41 -0500
Subject: [PATCH 051/213] Simplify the logic
---
lib/pleroma/plugs/http_security_plug.ex | 19 ++++++-------------
1 file changed, 6 insertions(+), 13 deletions(-)
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 3bf0b8ce7..13423ca3f 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -114,24 +114,15 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
add_source(acc, host)
end)
- media_proxy_base_url =
- if Config.get([:media_proxy, :base_url]),
- do: build_csp_param(Config.get([:media_proxy, :base_url]))
+ media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url]))
- upload_base_url =
- if Config.get([Pleroma.Upload, :base_url]),
- do: build_csp_param(Config.get([Pleroma.Upload, :base_url]))
+ upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url]))
- s3_endpoint =
- if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
- do: build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
+ s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
captcha_method = Config.get([Pleroma.Captcha, :method])
- captcha_endpoint =
- if Config.get([Pleroma.Captcha, :enabled]) &&
- captcha_method != "Pleroma.Captcha.Native",
- do: build_csp_param(Config.get([captcha_method, :endpoint]))
+ captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint]))
[]
|> add_source(media_proxy_base_url)
@@ -148,6 +139,8 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
+ defp build_csp_param(nil), do: nil
+
defp build_csp_param(url) when is_binary(url) do
%{host: host, scheme: scheme} = URI.parse(url)
From da4029391d217b1e2c151e69479de42e2221e02f Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Mon, 6 Jul 2020 11:28:08 -0500
Subject: [PATCH 052/213] IO list, not concatenation
---
lib/pleroma/plugs/http_security_plug.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 13423ca3f..472a3ff42 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -145,7 +145,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
%{host: host, scheme: scheme} = URI.parse(url)
if scheme do
- scheme <> "://" <> host
+ [scheme, "://", host]
end
end
From f6d09fafee83514889bbcf6531e0bc01e33b0b16 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Sun, 1 Mar 2020 09:48:32 +0100
Subject: [PATCH 053/213] Add support for remote favicons
---
lib/pleroma/user.ex | 30 +++++++++++++++++++
.../web/mastodon_api/views/account_view.ex | 3 +-
test/support/http_request_mock.ex | 4 +++
.../mastodon_api/views/account_view_test.exs | 2 ++
4 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index e98332744..25ea112a2 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -2253,4 +2253,34 @@ defmodule Pleroma.User do
|> Map.put(:bio, HTML.filter_tags(user.bio, filter))
|> Map.put(:fields, fields)
end
+
+ def get_cached_favicon(%User{} = user) do
+ key = "favicon:#{user.ap_id}"
+ Cachex.fetch!(:user_cache, key, fn _ -> get_favicon(user) end)
+ end
+
+ def get_cached_favicon(_user) do
+ nil
+ end
+
+ def get_favicon(user) do
+ try do
+ with url <- user.ap_id,
+ true <- is_binary(url),
+ {:ok, %Tesla.Env{body: html}} <- Pleroma.HTTP.get(url),
+ favicon_rel <-
+ html
+ |> Floki.parse_document!()
+ |> Floki.attribute("link[rel=icon]", "href")
+ |> List.first(),
+ favicon_url <- URI.merge(URI.parse(url), favicon_rel) |> to_string(),
+ true <- is_binary(favicon_url) do
+ favicon_url
+ else
+ _ -> nil
+ end
+ rescue
+ _ -> nil
+ end
+ 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 a6e64b4ab..efe835e3c 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -245,7 +245,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
hide_favorites: user.hide_favorites,
relationship: relationship,
skip_thread_containment: user.skip_thread_containment,
- background_image: image_url(user.background) |> MediaProxy.url()
+ background_image: image_url(user.background) |> MediaProxy.url(),
+ favicon: User.get_cached_favicon(user) |> MediaProxy.url()
}
}
|> maybe_put_role(user, opts[:for])
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index da04ac6f1..4d33c6250 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -1342,6 +1342,10 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/relay/relay.json")}}
end
+ def get("http://localhost:4001/users/" <> _, _, _, _) do
+ {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
+ end
+
def get(url, query, body, headers) do
{:error,
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 80b1f734c..e01a7c1ee 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -75,6 +75,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{
ap_id: user.ap_id,
background_image: "https://example.com/images/asuka_hospital.png",
+ favicon: nil,
confirmation_pending: false,
tags: [],
is_admin: false,
@@ -152,6 +153,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{
ap_id: user.ap_id,
background_image: nil,
+ favicon: nil,
confirmation_pending: false,
tags: [],
is_admin: false,
From 6a679d80c9030afa8327377928f8ac2fcf1a4a0e Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Mon, 2 Mar 2020 05:38:25 +0100
Subject: [PATCH 054/213] Move get_favicon to Pleroma.Instances, use /
---
lib/pleroma/application.ex | 1 +
lib/pleroma/instances.ex | 28 ++
lib/pleroma/user.ex | 30 --
.../web/mastodon_api/views/account_view.ex | 11 +-
.../https___osada.macgirvin.com.html | 301 ++++++++++++++++++
test/support/http_request_mock.ex | 10 +-
.../mastodon_api/views/account_view_test.exs | 6 +-
7 files changed, 353 insertions(+), 34 deletions(-)
create mode 100644 test/fixtures/tesla_mock/https___osada.macgirvin.com.html
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 9615af122..c7fc95f75 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -150,6 +150,7 @@ defmodule Pleroma.Application do
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
build_cachex("failed_proxy_url", limit: 2500),
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
+ build_cachex("instances", default_ttl: 25_000, ttl_interval: 1000, limit: 2500)
]
end
diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index 557e8decf..c9b1ed4ce 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -37,4 +37,32 @@ defmodule Pleroma.Instances do
url_or_host
end
end
+
+ def get_cached_favicon(instance_url) when is_binary(instance_url) do
+ Cachex.fetch!(:instances_cache, instance_url, fn _ -> get_favicon(instance_url) end)
+ end
+
+ def get_cached_favicon(_instance_url) do
+ nil
+ end
+
+ def get_favicon(instance_url) when is_binary(instance_url) do
+ try do
+ with {:ok, %Tesla.Env{body: html}} <-
+ Pleroma.HTTP.get(instance_url, [{:Accept, "text/html"}]),
+ favicon_rel <-
+ html
+ |> Floki.parse_document!()
+ |> Floki.attribute("link[rel=icon]", "href")
+ |> List.first(),
+ favicon_url <- URI.merge(URI.parse(instance_url), favicon_rel) |> to_string(),
+ true <- is_binary(favicon_url) do
+ favicon_url
+ else
+ _ -> nil
+ end
+ rescue
+ _ -> nil
+ end
+ end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 25ea112a2..e98332744 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -2253,34 +2253,4 @@ defmodule Pleroma.User do
|> Map.put(:bio, HTML.filter_tags(user.bio, filter))
|> Map.put(:fields, fields)
end
-
- def get_cached_favicon(%User{} = user) do
- key = "favicon:#{user.ap_id}"
- Cachex.fetch!(:user_cache, key, fn _ -> get_favicon(user) end)
- end
-
- def get_cached_favicon(_user) do
- nil
- end
-
- def get_favicon(user) do
- try do
- with url <- user.ap_id,
- true <- is_binary(url),
- {:ok, %Tesla.Env{body: html}} <- Pleroma.HTTP.get(url),
- favicon_rel <-
- html
- |> Floki.parse_document!()
- |> Floki.attribute("link[rel=icon]", "href")
- |> List.first(),
- favicon_url <- URI.merge(URI.parse(url), favicon_rel) |> to_string(),
- true <- is_binary(favicon_url) do
- favicon_url
- else
- _ -> nil
- end
- rescue
- _ -> nil
- end
- 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 efe835e3c..3ee50dfd0 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -204,6 +204,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
%{}
end
+ favicon =
+ user
+ |> Map.get(:ap_id, "")
+ |> URI.parse()
+ |> URI.merge("/")
+ |> to_string()
+ |> Pleroma.Instances.get_cached_favicon()
+ |> MediaProxy.url()
+
%{
id: to_string(user.id),
username: username_from_nickname(user.nickname),
@@ -246,7 +255,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
relationship: relationship,
skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url(),
- favicon: User.get_cached_favicon(user) |> MediaProxy.url()
+ favicon: favicon
}
}
|> maybe_put_role(user, opts[:for])
diff --git a/test/fixtures/tesla_mock/https___osada.macgirvin.com.html b/test/fixtures/tesla_mock/https___osada.macgirvin.com.html
new file mode 100644
index 000000000..880273d74
--- /dev/null
+++ b/test/fixtures/tesla_mock/https___osada.macgirvin.com.html
@@ -0,0 +1,301 @@
+
+
+
+ Osada
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Welcome to Osada
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 4d33c6250..19a202654 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -1342,10 +1342,18 @@ defmodule HttpRequestMock do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/relay/relay.json")}}
end
- def get("http://localhost:4001/users/" <> _, _, _, _) do
+ def get("http://localhost:4001/", _, "", Accept: "text/html") do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
end
+ def get("https://osada.macgirvin.com/", _, "", Accept: "text/html") do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/tesla_mock/https___osada.macgirvin.com.html")
+ }}
+ end
+
def get(url, query, body, headers) do
{:error,
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index e01a7c1ee..c4341cb28 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -75,7 +75,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{
ap_id: user.ap_id,
background_image: "https://example.com/images/asuka_hospital.png",
- favicon: nil,
+ favicon:
+ "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
confirmation_pending: false,
tags: [],
is_admin: false,
@@ -153,7 +154,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
pleroma: %{
ap_id: user.ap_id,
background_image: nil,
- favicon: nil,
+ favicon:
+ "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png",
confirmation_pending: false,
tags: [],
is_admin: false,
From 013e2c505786dff311bcc8bf23631d6a1a1636ef Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Tue, 7 Jul 2020 11:13:38 +0200
Subject: [PATCH 055/213] Use instances table instead of Cachex
---
lib/pleroma/application.ex | 1 -
lib/pleroma/instances.ex | 28 ----------
lib/pleroma/instances/instance.ex | 55 ++++++++++++++++++-
.../web/mastodon_api/views/account_view.ex | 3 +-
.../20200707112859_instances_add_favicon.exs | 10 ++++
5 files changed, 65 insertions(+), 32 deletions(-)
create mode 100644 priv/repo/migrations/20200707112859_instances_add_favicon.exs
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index c7fc95f75..9615af122 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -150,7 +150,6 @@ defmodule Pleroma.Application do
build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
build_cachex("failed_proxy_url", limit: 2500),
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000)
- build_cachex("instances", default_ttl: 25_000, ttl_interval: 1000, limit: 2500)
]
end
diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index c9b1ed4ce..557e8decf 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -37,32 +37,4 @@ defmodule Pleroma.Instances do
url_or_host
end
end
-
- def get_cached_favicon(instance_url) when is_binary(instance_url) do
- Cachex.fetch!(:instances_cache, instance_url, fn _ -> get_favicon(instance_url) end)
- end
-
- def get_cached_favicon(_instance_url) do
- nil
- end
-
- def get_favicon(instance_url) when is_binary(instance_url) do
- try do
- with {:ok, %Tesla.Env{body: html}} <-
- Pleroma.HTTP.get(instance_url, [{:Accept, "text/html"}]),
- favicon_rel <-
- html
- |> Floki.parse_document!()
- |> Floki.attribute("link[rel=icon]", "href")
- |> List.first(),
- favicon_url <- URI.merge(URI.parse(instance_url), favicon_rel) |> to_string(),
- true <- is_binary(favicon_url) do
- favicon_url
- else
- _ -> nil
- end
- rescue
- _ -> nil
- end
- end
end
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 74458c09a..b97e229e5 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -17,6 +17,8 @@ defmodule Pleroma.Instances.Instance do
schema "instances" do
field(:host, :string)
field(:unreachable_since, :naive_datetime_usec)
+ field(:favicon, :string)
+ field(:favicon_updated_at, :naive_datetime)
timestamps()
end
@@ -25,7 +27,7 @@ defmodule Pleroma.Instances.Instance do
def changeset(struct, params \\ %{}) do
struct
- |> cast(params, [:host, :unreachable_since])
+ |> cast(params, [:host, :unreachable_since, :favicon, :favicon_updated_at])
|> validate_required([:host])
|> unique_constraint(:host)
end
@@ -120,4 +122,55 @@ defmodule Pleroma.Instances.Instance do
end
defp parse_datetime(datetime), do: datetime
+
+ def get_or_update_favicon(%URI{host: host} = instance_uri) do
+ existing_record = Repo.get_by(Instance, %{host: host})
+ now = NaiveDateTime.utc_now()
+
+ if existing_record && existing_record.favicon &&
+ NaiveDateTime.diff(now, existing_record.favicon_updated_at) < 86_400 do
+ existing_record.favicon
+ else
+ favicon = scrape_favicon(instance_uri)
+
+ cond do
+ is_binary(favicon) && existing_record ->
+ existing_record
+ |> changeset(%{favicon: favicon, favicon_updated_at: now})
+ |> Repo.update()
+
+ favicon
+
+ is_binary(favicon) ->
+ %Instance{}
+ |> changeset(%{host: host, favicon: favicon, favicon_updated_at: now})
+ |> Repo.insert()
+
+ favicon
+
+ true ->
+ nil
+ end
+ end
+ end
+
+ defp scrape_favicon(%URI{} = instance_uri) do
+ try do
+ with {:ok, %Tesla.Env{body: html}} <-
+ Pleroma.HTTP.get(to_string(instance_uri), [{:Accept, "text/html"}]),
+ favicon_rel <-
+ html
+ |> Floki.parse_document!()
+ |> Floki.attribute("link[rel=icon]", "href")
+ |> List.first(),
+ favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(),
+ true <- is_binary(favicon) do
+ favicon
+ else
+ _ -> nil
+ end
+ rescue
+ _ -> nil
+ end
+ 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 3ee50dfd0..db5739254 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -209,8 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> Map.get(:ap_id, "")
|> URI.parse()
|> URI.merge("/")
- |> to_string()
- |> Pleroma.Instances.get_cached_favicon()
+ |> Pleroma.Instances.Instance.get_or_update_favicon()
|> MediaProxy.url()
%{
diff --git a/priv/repo/migrations/20200707112859_instances_add_favicon.exs b/priv/repo/migrations/20200707112859_instances_add_favicon.exs
new file mode 100644
index 000000000..5538749dc
--- /dev/null
+++ b/priv/repo/migrations/20200707112859_instances_add_favicon.exs
@@ -0,0 +1,10 @@
+defmodule Pleroma.Repo.Migrations.InstancesAddFavicon do
+ use Ecto.Migration
+
+ def change do
+ alter table(:instances) do
+ add(:favicon, :string)
+ add(:favicon_updated_at, :naive_datetime)
+ end
+ end
+end
From 8c9df2d2e6a5a3639f6b411cd3e9b57248a0650c Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Tue, 7 Jul 2020 12:07:30 +0200
Subject: [PATCH 056/213] instance: Prevent loop of updates
---
lib/pleroma/instances/instance.ex | 29 +++++++++++------------------
1 file changed, 11 insertions(+), 18 deletions(-)
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index b97e229e5..a1f935232 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -127,30 +127,23 @@ defmodule Pleroma.Instances.Instance do
existing_record = Repo.get_by(Instance, %{host: host})
now = NaiveDateTime.utc_now()
- if existing_record && existing_record.favicon &&
+ if existing_record && existing_record.favicon_updated_at &&
NaiveDateTime.diff(now, existing_record.favicon_updated_at) < 86_400 do
existing_record.favicon
else
favicon = scrape_favicon(instance_uri)
- cond do
- is_binary(favicon) && existing_record ->
- existing_record
- |> changeset(%{favicon: favicon, favicon_updated_at: now})
- |> Repo.update()
-
- favicon
-
- is_binary(favicon) ->
- %Instance{}
- |> changeset(%{host: host, favicon: favicon, favicon_updated_at: now})
- |> Repo.insert()
-
- favicon
-
- true ->
- nil
+ if existing_record do
+ existing_record
+ |> changeset(%{favicon: favicon, favicon_updated_at: now})
+ |> Repo.update()
+ else
+ %Instance{}
+ |> changeset(%{host: host, favicon: favicon, favicon_updated_at: now})
+ |> Repo.insert()
end
+
+ favicon
end
end
From 312fc55f14e1b7f88ec43b72c577bf5df595beac Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Wed, 8 Jul 2020 05:56:24 +0200
Subject: [PATCH 057/213] Add [:instances_favicons, :enabled] setting, defaults
to false
---
config/config.exs | 2 ++
config/description.exs | 13 ++++++++++++
config/test.exs | 2 ++
docs/configuration/cheatsheet.md | 6 ++++++
.../web/mastodon_api/views/account_view.ex | 16 +++++++++------
.../mastodon_api/views/account_view_test.exs | 20 +++++++++++++++++++
6 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 458d3a99a..3577cd101 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -706,6 +706,8 @@ config :tzdata, :http_client, Pleroma.HTTP.Tzdata
config :ex_aws, http_client: Pleroma.HTTP.ExAws
+config :pleroma, :instances_favicons, enabled: false
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/config/description.exs b/config/description.exs
index 650610fbe..7c432d67d 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -3447,5 +3447,18 @@ config :pleroma, :config_description, [
suggestions: [false]
}
]
+ },
+ %{
+ group: :pleroma,
+ key: :instances_favicons,
+ type: :group,
+ description: "Control favicons for instances",
+ children: [
+ %{
+ key: :enabled,
+ type: :boolean,
+ description: "Allow/disallow displaying and getting instances favicons"
+ }
+ ]
}
]
diff --git a/config/test.exs b/config/test.exs
index e38b9967d..e6596e0bc 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -111,6 +111,8 @@ config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
+config :pleroma, :instances_favicons, enabled: true
+
if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs"
else
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 6b640cebc..7a3200e01 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -987,3 +987,9 @@ Restrict access for unauthenticated users to timelines (public and federate), us
## Pleroma.Web.ApiSpec.CastAndValidate
* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`.
+
+## :instances_favicons
+
+Control favicons for instances.
+
+* `enabled`: Allow/disallow displaying and getting instances favicons
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index db5739254..2feba4778 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -205,12 +205,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
favicon =
- user
- |> Map.get(:ap_id, "")
- |> URI.parse()
- |> URI.merge("/")
- |> Pleroma.Instances.Instance.get_or_update_favicon()
- |> MediaProxy.url()
+ if Pleroma.Config.get([:instances_favicons, :enabled]) do
+ user
+ |> Map.get(:ap_id, "")
+ |> URI.parse()
+ |> URI.merge("/")
+ |> Pleroma.Instances.Instance.get_or_update_favicon()
+ |> MediaProxy.url()
+ else
+ nil
+ end
%{
id: to_string(user.id),
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index c4341cb28..ac6d50e3a 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
use Pleroma.DataCase
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI
@@ -18,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
:ok
end
+ setup do: clear_config([:instances_favicons, :enabled])
+
test "Represent a user account" do
background_image = %{
"url" => [%{"href" => "https://example.com/images/asuka_hospital.png"}]
@@ -94,6 +97,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
assert expected == AccountView.render("show.json", %{user: user})
end
+ test "Favicon is nil when :instances_favicons is disabled" do
+ user = insert(:user)
+
+ Config.put([:instances_favicons, :enabled], true)
+
+ assert %{
+ pleroma: %{
+ favicon:
+ "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png"
+ }
+ } = AccountView.render("show.json", %{user: user})
+
+ Config.put([:instances_favicons, :enabled], false)
+
+ assert %{pleroma: %{favicon: nil}} = AccountView.render("show.json", %{user: user})
+ end
+
test "Represent the user account for the account owner" do
user = insert(:user)
From 31fef95e35d3cbc2d65c76a57ba8f4047809ce1b Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Wed, 8 Jul 2020 06:09:39 +0200
Subject: [PATCH 058/213] Add changelog and documentation
---
CHANGELOG.md | 2 ++
docs/API/differences_in_mastoapi_responses.md | 1 +
lib/pleroma/web/api_spec/schemas/account.ex | 6 ++++++
3 files changed, 9 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92d8c3d8e..0f3447069 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added `:reject_deletes` group to SimplePolicy
- MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances
- Support pagination in emoji packs API (for packs and for files in pack)
+- Support for viewing instances favicons next to posts and accounts
API Changes
@@ -65,6 +66,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Add support for filtering replies in public and home timelines.
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`.
- Mastodon API: Support irreversible property for filters.
+- Mastodon API: Add pleroma.favicon field to accounts.
- Admin API: endpoints for create/update/delete OAuth Apps.
- Admin API: endpoint for status view.
- OTP: Add command to reload emoji packs
diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 29141ed0c..03c7f4608 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -71,6 +71,7 @@ Has these additional fields under the `pleroma` object:
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
- `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned.
+- `favicon`: nullable URL string, Favicon image of the user's instance
### Source
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 84f18f1b6..e6f163cb7 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -102,6 +102,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
type: :object,
description:
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
+ },
+ favicon: %Schema{
+ type: :string,
+ format: :uri,
+ nullable: true,
+ description: "Favicon image of the user's instance"
}
}
},
From e341f817850e51a29ec45607563323b6660f8da4 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Tue, 7 Jul 2020 09:10:02 +0300
Subject: [PATCH 059/213] fixed delete `Like` activity in remove user
---
lib/pleroma/web/activity_pub/side_effects.ex | 21 +++++++++++++----
test/tasks/user_test.exs | 24 ++++++++++++++++++++
2 files changed, 40 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 61feeae4d..70746f341 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -209,14 +209,20 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, object}
end
- def handle_undoing(%{data: %{"type" => "Like"}} = object) do
- with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
- {:ok, _} <- Utils.remove_like_from_object(object, liked_object),
- {:ok, _} <- Repo.delete(object) do
- :ok
+ defp undo_like(nil, object), do: delete_object(object)
+
+ defp undo_like(%Object{} = liked_object, object) do
+ with {:ok, _} <- Utils.remove_like_from_object(object, liked_object) do
+ delete_object(object)
end
end
+ def handle_undoing(%{data: %{"type" => "Like"}} = object) do
+ object.data["object"]
+ |> Object.get_by_ap_id()
+ |> undo_like(object)
+ end
+
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
{:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
@@ -246,6 +252,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
+ @spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()}
+ defp delete_object(object) do
+ with {:ok, _} <- Repo.delete(object), do: :ok
+ end
+
defp send_notifications(meta) do
Keyword.get(meta, :notifications, [])
|> Enum.each(fn notification ->
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index 9220d23fc..c962819db 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -110,7 +110,30 @@ defmodule Mix.Tasks.Pleroma.UserTest do
test "a remote user's create activity is deleted when the object has been pruned" do
user = insert(:user)
+ user2 = insert(:user)
+
{:ok, post} = CommonAPI.post(user, %{status: "uguu"})
+ {:ok, post2} = CommonAPI.post(user2, %{status: "test"})
+ obj = Object.normalize(post2)
+
+ {:ok, like_object, meta} = Pleroma.Web.ActivityPub.Builder.like(user, obj)
+
+ {:ok, like_activity, _meta} =
+ Pleroma.Web.ActivityPub.Pipeline.common_pipeline(
+ like_object,
+ Keyword.put(meta, :local, true)
+ )
+
+ like_obj = Pleroma.Object.get_by_ap_id(like_activity.data["object"])
+
+ data =
+ Map.merge(like_activity.data, %{"object" => "tag:gnusocial.cc,2019-01-09:noticeId=210716"})
+
+ like_activity
+ |> Ecto.Changeset.change(data: data)
+ |> Repo.update()
+
+ Repo.delete(like_obj)
clear_config([:instance, :federating], true)
@@ -127,6 +150,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
assert %{deactivated: true} = User.get_by_nickname(user.nickname)
assert called(Pleroma.Web.Federator.publish(:_))
+ refute Pleroma.Repo.get(Pleroma.Activity, like_activity.id)
end
refute Activity.get_by_id(post.id)
From c0385cf47ae9c2dac527387225dee7d45dd33d8c Mon Sep 17 00:00:00 2001
From: lain
Date: Wed, 8 Jul 2020 11:52:29 +0200
Subject: [PATCH 060/213] AccountController: Fix muting / unmuting reblogs.
---
.../api_spec/operations/account_operation.ex | 23 ++++++++----
.../controllers/account_controller.ex | 2 +-
.../controllers/account_controller_test.exs | 37 ++++++++++++++++++-
3 files changed, 52 insertions(+), 10 deletions(-)
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 9bde8fc0d..989bab122 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -203,14 +203,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
security: [%{"oAuth" => ["follow", "write:follows"]}],
description: "Follow the given account",
parameters: [
- %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
- Operation.parameter(
- :reblogs,
- :query,
- BooleanLike,
- "Receive this account's reblogs in home timeline? Defaults to true."
- )
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
],
+ requestBody:
+ request_body(
+ "Parameters",
+ %Schema{
+ type: :object,
+ properties: %{
+ reblogs: %Schema{
+ type: :boolean,
+ description: "Receive this account's reblogs in home timeline? Defaults to true.",
+ default: true
+ }
+ }
+ },
+ required: false
+ ),
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index d4532258c..fd89faf02 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -353,7 +353,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
{:error, "Can not follow yourself"}
end
- def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
+ def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do
with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
render(conn, "relationship.json", user: follower, target: followed)
else
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 260ad2306..f102c0cd2 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -708,7 +708,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
followed = insert(:user)
other_user = insert(:user)
- ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false")
+ ret_conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: false})
assert %{"showing_reblogs" => false} = json_response_and_validate_schema(ret_conn, 200)
@@ -722,7 +725,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert %{"showing_reblogs" => true} =
conn
- |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: true})
|> json_response_and_validate_schema(200)
assert [%{"id" => ^reblog_id}] =
@@ -731,6 +735,35 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|> json_response(200)
end
+ test "following with reblogs" do
+ %{conn: conn} = oauth_access(["follow", "read:statuses"])
+ followed = insert(:user)
+ other_user = insert(:user)
+
+ ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow")
+
+ assert %{"showing_reblogs" => true} = json_response_and_validate_schema(ret_conn, 200)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
+ {:ok, %{id: reblog_id}} = CommonAPI.repeat(activity.id, followed)
+
+ assert [%{"id" => ^reblog_id}] =
+ conn
+ |> get("/api/v1/timelines/home")
+ |> json_response(200)
+
+ assert %{"showing_reblogs" => false} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{followed.id}/follow", %{reblogs: false})
+ |> json_response_and_validate_schema(200)
+
+ assert [] ==
+ conn
+ |> get("/api/v1/timelines/home")
+ |> json_response(200)
+ end
+
test "following / unfollowing errors", %{user: user, conn: conn} do
# self follow
conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
From 704a3830556d94e0dbc39873480e9ba95a143be9 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Wed, 8 Jul 2020 13:14:18 +0300
Subject: [PATCH 061/213] Improved search results for localized nickname match.
Tweaked user search to rank nickname matches higher than name matches.
---
lib/pleroma/user/search.ex | 8 +++++++-
test/tasks/user_test.exs | 16 ++++++++--------
test/user_search_test.exs | 35 +++++++++++++++++++++++++++++------
3 files changed, 44 insertions(+), 15 deletions(-)
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index 42ff1de78..7ff1c7e24 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -88,15 +88,21 @@ defmodule Pleroma.User.Search do
|> Enum.join(" | ")
end
+ # Considers nickname match, localized nickname match, name match; preferences nickname match
defp trigram_rank(query, query_string) do
from(
u in query,
select_merge: %{
search_rank:
fragment(
- "similarity(?, trim(? || ' ' || coalesce(?, '')))",
+ "similarity(?, ?) + \
+ similarity(?, regexp_replace(?, '@.+', '')) + \
+ similarity(?, trim(coalesce(?, '')))",
^query_string,
u.nickname,
+ ^query_string,
+ u.nickname,
+ ^query_string,
u.name
)
}
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index 9220d23fc..7bb49b038 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -464,17 +464,17 @@ defmodule Mix.Tasks.Pleroma.UserTest do
moot = insert(:user, nickname: "moot")
kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon")
- {:ok, user} = User.follow(user, kawen)
+ {:ok, user} = User.follow(user, moon)
assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id)
- res = User.search("moo") |> Enum.map(& &1.id)
- assert moon.id in res
- assert moot.id in res
- assert kawen.id in res
- assert [moon.id, kawen.id] == User.Search.search("moon fediverse") |> Enum.map(& &1.id)
- assert [kawen.id, moon.id] ==
- User.Search.search("moon fediverse", for_user: user) |> Enum.map(& &1.id)
+ res = User.search("moo") |> Enum.map(& &1.id)
+ assert Enum.sort([moon.id, moot.id, kawen.id]) == Enum.sort(res)
+
+ assert [kawen.id, moon.id] == User.Search.search("expert fediverse") |> Enum.map(& &1.id)
+
+ assert [moon.id, kawen.id] ==
+ User.Search.search("expert fediverse", for_user: user) |> Enum.map(& &1.id)
end
end
diff --git a/test/user_search_test.exs b/test/user_search_test.exs
index f030523d3..758822072 100644
--- a/test/user_search_test.exs
+++ b/test/user_search_test.exs
@@ -46,30 +46,53 @@ defmodule Pleroma.UserSearchTest do
assert length(User.search("john", limit: 3, offset: 3)) == 2
end
- test "finds a user by full or partial nickname" do
+ defp clear_virtual_fields(user) do
+ Map.merge(user, %{search_rank: nil, search_type: nil})
+ end
+
+ test "finds a user by full nickname or its leading fragment" do
user = insert(:user, %{nickname: "john"})
Enum.each(["john", "jo", "j"], fn query ->
assert user ==
User.search(query)
|> List.first()
- |> Map.put(:search_rank, nil)
- |> Map.put(:search_type, nil)
+ |> clear_virtual_fields()
end)
end
- test "finds a user by full or partial name" do
+ test "finds a user by full name or leading fragment(s) of its words" do
user = insert(:user, %{name: "John Doe"})
Enum.each(["John Doe", "JOHN", "doe", "j d", "j", "d"], fn query ->
assert user ==
User.search(query)
|> List.first()
- |> Map.put(:search_rank, nil)
- |> Map.put(:search_type, nil)
+ |> clear_virtual_fields()
end)
end
+ test "is not [yet] capable of matching by non-leading fragments (e.g. by domain)" do
+ user1 = insert(:user, %{nickname: "iamthedude"})
+ insert(:user, %{nickname: "arandom@dude.com"})
+
+ assert [] == User.search("dude")
+
+ # Matching by leading fragment works, though
+ user1_id = user1.id
+ assert ^user1_id = User.search("iam") |> List.first() |> Map.get(:id)
+ end
+
+ test "ranks full nickname match higher than full name match" do
+ nicknamed_user = insert(:user, %{nickname: "hj@shigusegubu.club"})
+ named_user = insert(:user, %{nickname: "xyz@sample.com", name: "HJ"})
+
+ results = User.search("hj")
+
+ assert [nicknamed_user.id, named_user.id] == Enum.map(results, & &1.id)
+ assert Enum.at(results, 0).search_rank > Enum.at(results, 1).search_rank
+ end
+
test "finds users, considering density of matched tokens" do
u1 = insert(:user, %{name: "Bar Bar plus Word Word"})
u2 = insert(:user, %{name: "Word Word Bar Bar Bar"})
From 29fa75d00d1f550461b2ab1e59554e134208d419 Mon Sep 17 00:00:00 2001
From: lain
Date: Wed, 8 Jul 2020 14:29:29 +0200
Subject: [PATCH 062/213] Notification: For follows, notify the followed.
---
lib/pleroma/notification.ex | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index fcb2144ae..32bcfcaba 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -497,6 +497,10 @@ defmodule Pleroma.Notification do
end
end
+ def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do
+ [object_id]
+ end
+
def get_potential_receiver_ap_ids(activity) do
[]
|> Utils.maybe_notify_to_recipients(activity)
From 172f4aff8ef573c54902dc8fa135d69f50fea47c Mon Sep 17 00:00:00 2001
From: lain
Date: Wed, 8 Jul 2020 14:30:53 +0200
Subject: [PATCH 063/213] Transmogrifier: Move following to the pipeline.
---
.../object_validators/follow_validator.ex | 2 +
lib/pleroma/web/activity_pub/side_effects.ex | 60 ++++++++++++++++++
.../web/activity_pub/transmogrifier.ex | 62 +------------------
.../transmogrifier/follow_handling_test.exs | 2 +-
4 files changed, 64 insertions(+), 62 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
index 2035ad9ba..ca2724616 100644
--- a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
+ field(:state, :string, default: "pending")
end
def cast_data(data) do
@@ -30,6 +31,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Follow"])
+ |> validate_inclusion(:state, ~w{pending reject accept})
|> validate_actor_presence()
|> validate_actor_presence(field_name: :object)
end
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 61feeae4d..284560913 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Activity.Ir.Topics
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
+ alias Pleroma.FollowingRelationship
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
@@ -21,6 +22,65 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
def handle(object, meta \\ [])
+ # Tasks this handle
+ # - Follows if possible
+ # - Sends a notification
+ # - Generates accept or reject if appropriate
+ def handle(
+ %{
+ data: %{
+ "id" => follow_id,
+ "type" => "Follow",
+ "object" => followed_user,
+ "actor" => following_user
+ }
+ } = object,
+ meta
+ ) do
+ with %User{} = follower <- User.get_cached_by_ap_id(following_user),
+ %User{} = followed <- User.get_cached_by_ap_id(followed_user),
+ {_, {:ok, _}, _, _} <-
+ {:following, User.follow(follower, followed, :follow_pending), follower, followed} do
+ if followed.local && !followed.locked do
+ Utils.update_follow_state_for_all(object, "accept")
+ FollowingRelationship.update(follower, followed, :follow_accept)
+
+ %{
+ to: [following_user],
+ actor: followed,
+ object: follow_id,
+ local: true
+ }
+ |> ActivityPub.accept()
+ end
+ else
+ {:following, {:error, _}, follower, followed} ->
+ Utils.update_follow_state_for_all(object, "reject")
+ FollowingRelationship.update(follower, followed, :follow_reject)
+
+ if followed.local do
+ %{
+ to: [follower.ap_id],
+ actor: followed,
+ object: follow_id,
+ local: true
+ }
+ |> ActivityPub.reject()
+ end
+
+ _ ->
+ nil
+ end
+
+ {:ok, notifications} = Notification.create_notifications(object, do_send: false)
+
+ meta =
+ meta
+ |> add_notifications(notifications)
+
+ {:ok, object, meta}
+ end
+
# Tasks this handles:
# - Unfollow and block
def handle(
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 117e930b3..884646ceb 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -529,66 +529,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
- def handle_incoming(
- %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
- _options
- ) do
- with %User{local: true} = followed <-
- User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
- {:ok, %User{} = follower} <-
- User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
- {:ok, activity} <-
- ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
- with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
- {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
- {_, false} <- {:user_locked, User.locked?(followed)},
- {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
- {_, {:ok, _}} <-
- {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
- {:ok, _relationship} <-
- FollowingRelationship.update(follower, followed, :follow_accept) do
- ActivityPub.accept(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
- else
- {:user_blocked, true} ->
- {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
-
- ActivityPub.reject(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
-
- {:follow, {:error, _}} ->
- {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
-
- ActivityPub.reject(%{
- to: [follower.ap_id],
- actor: followed,
- object: data,
- local: true
- })
-
- {:user_locked, true} ->
- {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
- :noop
- end
-
- ActivityPub.notify_and_stream(activity)
- {:ok, activity}
- else
- _e ->
- :error
- end
- end
-
def handle_incoming(
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
_options
@@ -696,7 +636,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => type} = data,
_options
)
- when type in ~w{Update Block} do
+ when type in ~w{Update Block Follow} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
index 06c39eed6..17e764ca1 100644
--- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs
+++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
@@ -160,7 +160,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
|> Poison.decode!()
|> Map.put("object", user.ap_id)
- with_mock Pleroma.User, [:passthrough], follow: fn _, _ -> {:error, :testing} end do
+ with_mock Pleroma.User, [:passthrough], follow: fn _, _, _ -> {:error, :testing} end do
{:ok, %Activity{data: %{"id" => id}}} = Transmogrifier.handle_incoming(data)
%Activity{} = activity = Activity.get_by_ap_id(id)
From 72ad3a66f48d4500be1f25dd7b02b834399d3bbe Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Fri, 3 Jul 2020 19:18:08 +0300
Subject: [PATCH 064/213] don't fully start pleroma in mix tasks
---
lib/mix/pleroma.ex | 20 +++++++++++++++++++-
lib/mix/tasks/pleroma/digest.ex | 2 ++
lib/mix/tasks/pleroma/email.ex | 1 +
lib/mix/tasks/pleroma/relay.ex | 3 +++
lib/pleroma/application.ex | 3 ++-
5 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 3ad6edbfb..553c74c25 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Pleroma do
+ @apps [:restarter, :ecto, :ecto_sql, :postgrex, :db_connection, :cachex]
+ @cachex_childs ["object", "user"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
@@ -11,7 +13,23 @@ defmodule Mix.Pleroma do
Application.put_env(:logger, :console, level: :debug)
end
- {:ok, _} = Application.ensure_all_started(:pleroma)
+ apps =
+ if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
+ [:gun | @apps]
+ else
+ [:hackney | @apps]
+ end
+
+ Enum.each(apps, &Application.ensure_all_started/1)
+
+ childs = [Pleroma.Repo, Pleroma.Config.TransferTask, Pleroma.Web.Endpoint]
+
+ cachex_childs = Enum.map(@cachex_childs, &Pleroma.Application.build_cachex(&1, []))
+
+ Supervisor.start_link(childs ++ cachex_childs,
+ strategy: :one_for_one,
+ name: Pleroma.Supervisor
+ )
if Pleroma.Config.get(:env) not in [:test, :benchmark] do
pleroma_rebooted?()
diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex
index 3595f912d..8bde2d4f2 100644
--- a/lib/mix/tasks/pleroma/digest.ex
+++ b/lib/mix/tasks/pleroma/digest.ex
@@ -7,6 +7,8 @@ defmodule Mix.Tasks.Pleroma.Digest do
def run(["test", nickname | opts]) do
Mix.Pleroma.start_pleroma()
+ Application.ensure_all_started(:timex)
+ Application.ensure_all_started(:swoosh)
user = Pleroma.User.get_by_nickname(nickname)
diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex
index d3fac6ec8..16fe31431 100644
--- a/lib/mix/tasks/pleroma/email.ex
+++ b/lib/mix/tasks/pleroma/email.ex
@@ -7,6 +7,7 @@ defmodule Mix.Tasks.Pleroma.Email do
def run(["test" | args]) do
Mix.Pleroma.start_pleroma()
+ Application.ensure_all_started(:swoosh)
{options, [], []} =
OptionParser.parse(
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index c3312507e..b67d256c3 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -12,6 +12,7 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["follow", target]) do
start_pleroma()
+ Application.ensure_all_started(:flake_id)
with {:ok, _activity} <- Relay.follow(target) do
# put this task to sleep to allow the genserver to push out the messages
@@ -23,6 +24,7 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["unfollow", target]) do
start_pleroma()
+ Application.ensure_all_started(:flake_id)
with {:ok, _activity} <- Relay.unfollow(target) do
# put this task to sleep to allow the genserver to push out the messages
@@ -34,6 +36,7 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["list"]) do
start_pleroma()
+ Application.ensure_all_started(:flake_id)
with {:ok, list} <- Relay.list(true) do
list |> Enum.each(&shell_info(&1))
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 9615af122..7eb629abf 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -162,7 +162,8 @@ defmodule Pleroma.Application do
defp seconds_valid_interval,
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
- defp build_cachex(type, opts),
+ @spec build_cachex(String.t(), keyword()) :: map()
+ def build_cachex(type, opts),
do: %{
id: String.to_atom("cachex_" <> type),
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
From b28cc154596ad9cbf5ef9708a5967672c61ddbdc Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Fri, 3 Jul 2020 20:12:00 +0300
Subject: [PATCH 065/213] don't restart pleroma in mix tasks
---
lib/mix/pleroma.ex | 6 +++++-
lib/pleroma/config/transfer_task.ex | 4 ++--
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 553c74c25..0fbb6f1cd 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -22,7 +22,11 @@ defmodule Mix.Pleroma do
Enum.each(apps, &Application.ensure_all_started/1)
- childs = [Pleroma.Repo, Pleroma.Config.TransferTask, Pleroma.Web.Endpoint]
+ childs = [
+ Pleroma.Repo,
+ {Pleroma.Config.TransferTask, false},
+ Pleroma.Web.Endpoint
+ ]
cachex_childs = Enum.map(@cachex_childs, &Pleroma.Application.build_cachex(&1, []))
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
index eb86b8ff4..a0d7b7d71 100644
--- a/lib/pleroma/config/transfer_task.ex
+++ b/lib/pleroma/config/transfer_task.ex
@@ -31,8 +31,8 @@ defmodule Pleroma.Config.TransferTask do
{:pleroma, :gopher, [:enabled]}
]
- def start_link(_) do
- load_and_update_env()
+ def start_link(restart_pleroma? \\ true) do
+ load_and_update_env([], restart_pleroma?)
if Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
:ignore
end
From 9dda8b542723afae8dd5493b4082b8524873a14d Mon Sep 17 00:00:00 2001
From: lain
Date: Wed, 8 Jul 2020 15:40:56 +0200
Subject: [PATCH 066/213] CommonAPI: Switch to pipeline for following.
---
lib/pleroma/web/activity_pub/side_effects.ex | 6 +++++-
lib/pleroma/web/common_api/common_api.ex | 10 +++++++---
test/web/mastodon_api/mastodon_api_test.exs | 2 +-
test/web/mastodon_api/views/account_view_test.exs | 3 +++
4 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index 284560913..de02baf0f 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -44,6 +44,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
if followed.local && !followed.locked do
Utils.update_follow_state_for_all(object, "accept")
FollowingRelationship.update(follower, followed, :follow_accept)
+ User.update_follower_count(followed)
+ User.update_following_count(follower)
%{
to: [following_user],
@@ -78,7 +80,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
meta
|> add_notifications(notifications)
- {:ok, object, meta}
+ updated_object = Activity.get_by_ap_id(follow_id)
+
+ {:ok, updated_object, meta}
end
# Tasks this handles:
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index fd7149079..4d5b0decf 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -101,10 +101,14 @@ defmodule Pleroma.Web.CommonAPI do
def follow(follower, followed) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
- with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
- {:ok, activity} <- ActivityPub.follow(follower, followed),
+ with {:ok, follow_data, _} <- Builder.follow(follower, followed),
+ {:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
- {:ok, follower, followed, activity}
+ if activity.data["state"] == "reject" do
+ {:error, :rejected}
+ else
+ {:ok, follower, followed, activity}
+ end
end
end
diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/web/mastodon_api/mastodon_api_test.exs
index a7f9c5205..c08be37d4 100644
--- a/test/web/mastodon_api/mastodon_api_test.exs
+++ b/test/web/mastodon_api/mastodon_api_test.exs
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do
follower = insert(:user)
user = insert(:user, local: true, deactivated: true)
{:error, error} = MastodonAPI.follow(follower, user)
- assert error == "Could not follow user: #{user.nickname} is deactivated."
+ assert error == :rejected
end
test "following for user" do
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index 80b1f734c..3e2e780e3 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -372,6 +372,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
user = insert(:user, hide_followers: true, hide_follows: true)
other_user = insert(:user)
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
+
+ assert User.following?(user, other_user)
+ assert Pleroma.FollowingRelationship.follower_count(other_user) == 1
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
assert %{
From 00e54f8fe7af098ba829f7f7cd5511569dcd1c0a Mon Sep 17 00:00:00 2001
From: lain
Date: Wed, 8 Jul 2020 17:07:24 +0200
Subject: [PATCH 067/213] ActivityPub: Remove `follow` and fix issues.
---
lib/pleroma/user.ex | 2 +-
lib/pleroma/web/activity_pub/activity_pub.ex | 22 -------------
lib/pleroma/web/activity_pub/relay.ex | 2 +-
test/tasks/relay_test.exs | 7 ++--
test/web/activity_pub/activity_pub_test.exs | 32 +++----------------
test/web/activity_pub/relay_test.exs | 6 ++--
test/web/activity_pub/transmogrifier_test.exs | 11 +++----
test/web/activity_pub/utils_test.exs | 9 +++---
test/web/common_api/common_api_test.exs | 21 ++++++++----
.../follow_request_controller_test.exs | 8 ++---
10 files changed, 43 insertions(+), 77 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index e98332744..9d1314f81 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1543,7 +1543,7 @@ defmodule Pleroma.User do
fn followed_identifier ->
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
{:ok, follower} <- maybe_direct_follow(follower, followed),
- {:ok, _} <- ActivityPub.follow(follower, followed) do
+ {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
followed
else
err ->
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 8abbef487..1c2908805 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -322,28 +322,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- @spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
- {:ok, Activity.t()} | {:error, any()}
- def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
- with {:ok, result} <-
- Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do
- result
- end
- end
-
- defp do_follow(follower, followed, activity_id, local, opts) do
- skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false)
- data = make_follow_data(follower, followed, activity_id)
-
- with {:ok, activity} <- insert(data, local),
- _ <- skip_notify_and_stream || notify_and_stream(activity),
- :ok <- maybe_federate(activity) do
- {:ok, activity}
- else
- {:error, error} -> Repo.rollback(error)
- end
- end
-
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t()} | nil | {:error, any()}
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 484178edd..b09764d2b 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
def follow(target_instance) do
with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
- {:ok, activity} <- ActivityPub.follow(local_user, target_user) do
+ {:ok, _, _, activity} <- CommonAPI.follow(local_user, target_user) do
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity}
else
diff --git a/test/tasks/relay_test.exs b/test/tasks/relay_test.exs
index a8ba0658d..79ab72002 100644
--- a/test/tasks/relay_test.exs
+++ b/test/tasks/relay_test.exs
@@ -10,6 +10,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
alias Pleroma.Web.ActivityPub.Utils
use Pleroma.DataCase
+ import Pleroma.Factory
+
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -46,7 +48,8 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
describe "running unfollow" do
test "relay is unfollowed" do
- target_instance = "http://mastodon.example.org/users/admin"
+ user = insert(:user)
+ target_instance = user.ap_id
Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
@@ -71,7 +74,7 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
assert undo_activity.data["type"] == "Undo"
assert undo_activity.data["actor"] == local_user.ap_id
- assert undo_activity.data["object"] == cancelled_activity.data
+ assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"]
refute "#{target_instance}/followers" in User.following(local_user)
end
end
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index 17e12a1a7..38c98f658 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -669,7 +669,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
refute activity in activities
followed_user = insert(:user)
- ActivityPub.follow(user, followed_user)
+ CommonAPI.follow(user, followed_user)
{:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
@@ -1013,24 +1013,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
end
end
- describe "following / unfollowing" do
- test "it reverts follow activity" do
- follower = insert(:user)
- followed = insert(:user)
-
- with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
- assert {:error, :reverted} = ActivityPub.follow(follower, followed)
- end
-
- assert Repo.aggregate(Activity, :count, :id) == 0
- assert Repo.aggregate(Object, :count, :id) == 0
- end
-
+ describe "unfollowing" do
test "it reverts unfollow activity" do
follower = insert(:user)
followed = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
@@ -1043,21 +1031,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert activity.data["object"] == followed.ap_id
end
- test "creates a follow activity" do
- follower = insert(:user)
- followed = insert(:user)
-
- {:ok, activity} = ActivityPub.follow(follower, followed)
- assert activity.data["type"] == "Follow"
- assert activity.data["actor"] == follower.ap_id
- assert activity.data["object"] == followed.ap_id
- end
-
test "creates an undo activity for the last follow" do
follower = insert(:user)
followed = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
{:ok, activity} = ActivityPub.unfollow(follower, followed)
assert activity.data["type"] == "Undo"
@@ -1074,7 +1052,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
follower = insert(:user)
followed = insert(:user, %{locked: true})
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
{:ok, activity} = ActivityPub.unfollow(follower, followed)
assert activity.data["type"] == "Undo"
diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs
index b3b573c9b..9d657ac4f 100644
--- a/test/web/activity_pub/relay_test.exs
+++ b/test/web/activity_pub/relay_test.exs
@@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
alias Pleroma.Activity
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.CommonAPI
import ExUnit.CaptureLog
import Pleroma.Factory
@@ -53,8 +53,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
test "returns activity" do
user = insert(:user)
service_actor = Relay.get_actor()
- ActivityPub.follow(service_actor, user)
- Pleroma.User.follow(service_actor, user)
+ CommonAPI.follow(service_actor, user)
assert "#{user.ap_id}/followers" in User.following(service_actor)
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
@@ -74,6 +73,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
assert Relay.publish(activity) == {:error, "Not implemented"}
end
+ @tag capture_log: true
test "returns error when activity not public" do
activity = insert(:direct_note_activity)
assert Relay.publish(activity) == {:error, false}
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 01179206c..f7b7d1a9f 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
alias Pleroma.Object.Fetcher
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
@@ -452,7 +451,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
{:ok, follower} = User.follow(follower, followed)
assert User.following?(follower, followed) == true
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
@@ -482,7 +481,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
follower = insert(:user)
followed = insert(:user, locked: true)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
@@ -504,7 +503,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
follower = insert(:user)
followed = insert(:user, locked: true)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
accept_data =
File.read!("test/fixtures/mastodon-accept-activity.json")
@@ -569,7 +568,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
followed = insert(:user, locked: true)
{:ok, follower} = User.follow(follower, followed)
- {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, _follow_activity} = CommonAPI.follow(follower, followed)
assert User.following?(follower, followed) == true
@@ -595,7 +594,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
followed = insert(:user, locked: true)
{:ok, follower} = User.follow(follower, followed)
- {:ok, follow_activity} = ActivityPub.follow(follower, followed)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
assert User.following?(follower, followed) == true
diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
index 2f9ecb5a3..361dc5a41 100644
--- a/test/web/activity_pub/utils_test.exs
+++ b/test/web/activity_pub/utils_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI
@@ -197,8 +196,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
user = insert(:user, locked: true)
follower = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
data =
follow_activity_two.data
@@ -221,8 +220,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
user = insert(:user, locked: true)
follower = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
data =
follow_activity_two.data
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 908ee5484..7e11fede3 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -934,6 +934,15 @@ defmodule Pleroma.Web.CommonAPITest do
end
end
+ describe "follow/2" do
+ test "directly follows a non-locked local user" do
+ [follower, followed] = insert_pair(:user)
+ {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
+
+ assert User.following?(follower, followed)
+ end
+ end
+
describe "unfollow/2" do
test "also unsubscribes a user" do
[follower, followed] = insert_pair(:user)
@@ -998,9 +1007,9 @@ defmodule Pleroma.Web.CommonAPITest do
follower = insert(:user)
follower_two = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
assert follow_activity.data["state"] == "pending"
assert follow_activity_two.data["state"] == "pending"
@@ -1018,9 +1027,9 @@ defmodule Pleroma.Web.CommonAPITest do
follower = insert(:user)
follower_two = insert(:user)
- {:ok, follow_activity} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
- {:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
+ {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
+ {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
assert follow_activity.data["state"] == "pending"
assert follow_activity_two.data["state"] == "pending"
diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
index 44e12d15a..6749e0e83 100644
--- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs
+++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
import Pleroma.Factory
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
test "/api/v1/follow_requests works", %{user: user, conn: conn} do
other_user = insert(:user)
- {:ok, _activity} = ActivityPub.follow(other_user, user)
+ {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
assert User.following?(other_user, user) == false
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
other_user = insert(:user)
- {:ok, _activity} = ActivityPub.follow(other_user, user)
+ {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
user = User.get_cached_by_id(user.id)
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do
other_user = insert(:user)
- {:ok, _activity} = ActivityPub.follow(other_user, user)
+ {:ok, _, _, _activity} = CommonAPI.follow(other_user, user)
user = User.get_cached_by_id(user.id)
From af9eb7a2b10d2994f915f8a33c2f5f7a7db1128e Mon Sep 17 00:00:00 2001
From: stwf
Date: Wed, 8 Jul 2020 11:32:04 -0400
Subject: [PATCH 068/213] re-enable federation tests
---
.gitlab-ci.yml | 28 +++++++++++++---------------
1 file changed, 13 insertions(+), 15 deletions(-)
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b4bd59b43..6a2be879e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -63,21 +63,19 @@ unit-testing:
- mix ecto.migrate
- mix coveralls --preload-modules
-# Removed to fix CI issue. In this early state it wasn't adding much value anyway.
-# TODO Fix and reinstate federated testing
-# federated-testing:
-# stage: test
-# cache: *testing_cache_policy
-# services:
-# - name: minibikini/postgres-with-rum:12
-# alias: postgres
-# command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
-# script:
-# - mix deps.get
-# - mix ecto.create
-# - mix ecto.migrate
-# - epmd -daemon
-# - mix test --trace --only federated
+federated-testing:
+ stage: test
+ cache: *testing_cache_policy
+ services:
+ - name: minibikini/postgres-with-rum:12
+ alias: postgres
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ script:
+ - mix deps.get
+ - mix ecto.create
+ - mix ecto.migrate
+ - epmd -daemon
+ - mix test --trace --only federated
unit-testing-rum:
stage: test
From 123352ffa1c80aab658fca0c2276d1c06de43a02 Mon Sep 17 00:00:00 2001
From: Ivan Tashkinov
Date: Wed, 8 Jul 2020 22:50:15 +0300
Subject: [PATCH 069/213] Removed unused trigram index on `users`. Fixed
`users_fts_index` usage.
---
lib/pleroma/user/search.ex | 16 +++++++++++-----
.../20200708193702_drop_user_trigram_index.exs | 18 ++++++++++++++++++
test/user_search_test.exs | 12 ++++--------
3 files changed, 33 insertions(+), 13 deletions(-)
create mode 100644 priv/repo/migrations/20200708193702_drop_user_trigram_index.exs
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index 7ff1c7e24..d4fd31069 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -69,11 +69,15 @@ defmodule Pleroma.User.Search do
u in query,
where:
fragment(
+ # The fragment must _exactly_ match `users_fts_index`, otherwise the index won't work
"""
- (to_tsvector('simple', ?) || to_tsvector('simple', ?)) @@ to_tsquery('simple', ?)
+ (
+ setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
+ setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')
+ ) @@ to_tsquery('simple', ?)
""",
- u.name,
u.nickname,
+ u.name,
^query_string
)
)
@@ -95,9 +99,11 @@ defmodule Pleroma.User.Search do
select_merge: %{
search_rank:
fragment(
- "similarity(?, ?) + \
- similarity(?, regexp_replace(?, '@.+', '')) + \
- similarity(?, trim(coalesce(?, '')))",
+ """
+ similarity(?, ?) +
+ similarity(?, regexp_replace(?, '@.+', '')) +
+ similarity(?, trim(coalesce(?, '')))
+ """,
^query_string,
u.nickname,
^query_string,
diff --git a/priv/repo/migrations/20200708193702_drop_user_trigram_index.exs b/priv/repo/migrations/20200708193702_drop_user_trigram_index.exs
new file mode 100644
index 000000000..94efe323a
--- /dev/null
+++ b/priv/repo/migrations/20200708193702_drop_user_trigram_index.exs
@@ -0,0 +1,18 @@
+defmodule Pleroma.Repo.Migrations.DropUserTrigramIndex do
+ @moduledoc "Drops unused trigram index on `users` (FTS index is being used instead)"
+
+ use Ecto.Migration
+
+ def up do
+ drop_if_exists(index(:users, [], name: :users_trigram_index))
+ end
+
+ def down do
+ create_if_not_exists(
+ index(:users, ["(trim(nickname || ' ' || coalesce(name, ''))) gist_trgm_ops"],
+ name: :users_trigram_index,
+ using: :gist
+ )
+ )
+ end
+end
diff --git a/test/user_search_test.exs b/test/user_search_test.exs
index 758822072..559ba5966 100644
--- a/test/user_search_test.exs
+++ b/test/user_search_test.exs
@@ -72,15 +72,11 @@ defmodule Pleroma.UserSearchTest do
end)
end
- test "is not [yet] capable of matching by non-leading fragments (e.g. by domain)" do
- user1 = insert(:user, %{nickname: "iamthedude"})
- insert(:user, %{nickname: "arandom@dude.com"})
+ test "matches by leading fragment of user domain" do
+ user = insert(:user, %{nickname: "arandom@dude.com"})
+ insert(:user, %{nickname: "iamthedude"})
- assert [] == User.search("dude")
-
- # Matching by leading fragment works, though
- user1_id = user1.id
- assert ^user1_id = User.search("iam") |> List.first() |> Map.get(:id)
+ assert [user.id] == User.search("dud") |> Enum.map(& &1.id)
end
test "ranks full nickname match higher than full name match" do
From 33e62856367b2789fa287830676edd843ad0e5d4 Mon Sep 17 00:00:00 2001
From: Angelina Filippova
Date: Thu, 9 Jul 2020 01:33:23 +0300
Subject: [PATCH 070/213] Update types for :headers and :options settings in
MediaProxy Invalidation group
---
config/description.exs | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/config/description.exs b/config/description.exs
index 370af80a6..337f0d307 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1791,15 +1791,20 @@ config :pleroma, :config_description, [
},
%{
key: :headers,
- type: {:list, :tuple},
- description: "HTTP headers of request.",
+ type: {:keyword, :string},
+ description: "HTTP headers of request",
suggestions: [{"x-refresh", 1}]
},
%{
key: :options,
type: :keyword,
- description: "Request options.",
- suggestions: [params: %{ts: "xxx"}]
+ description: "Request options",
+ children: [
+ %{
+ key: :params,
+ type: {:keyword, :string}
+ }
+ ]
}
]
},
From 6e5497225280e8400462b3135112b3ce80b145db Mon Sep 17 00:00:00 2001
From: Dym Sohin
Date: Thu, 9 Jul 2020 03:15:51 +0000
Subject: [PATCH 071/213] added link to changelog, removed repetition
---
docs/administration/updating.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/administration/updating.md b/docs/administration/updating.md
index 2a08dac1f..c994f3f16 100644
--- a/docs/administration/updating.md
+++ b/docs/administration/updating.md
@@ -1,6 +1,6 @@
# Updating your instance
-You should **always check the release notes/changelog** in case there are config deprecations, special update special update steps, etc.
+You should **always check the [release notes/changelog](https://git.pleroma.social/pleroma/pleroma/-/releases)** in case there are config deprecations, special update steps, etc.
Besides that, doing the following is generally enough:
From 31259cabcc1423b8ea23b01bcc9f425d0b99b547 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Thu, 9 Jul 2020 07:16:52 +0300
Subject: [PATCH 072/213] fix test
---
test/tasks/user_test.exs | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index c962819db..2a3e62e26 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -124,16 +124,9 @@ defmodule Mix.Tasks.Pleroma.UserTest do
Keyword.put(meta, :local, true)
)
- like_obj = Pleroma.Object.get_by_ap_id(like_activity.data["object"])
-
- data =
- Map.merge(like_activity.data, %{"object" => "tag:gnusocial.cc,2019-01-09:noticeId=210716"})
-
- like_activity
- |> Ecto.Changeset.change(data: data)
- |> Repo.update()
-
- Repo.delete(like_obj)
+ like_activity.data["object"]
+ |> Pleroma.Object.get_by_ap_id()
+ |> Repo.delete()
clear_config([:instance, :federating], true)
From 4cbafcef0c3a0ce9ddf888b558c6691afb96ad94 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Thu, 9 Jul 2020 11:17:43 +0300
Subject: [PATCH 073/213] load default config in mix tasks
---
lib/mix/pleroma.ex | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index 0fbb6f1cd..de16cc52c 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -7,6 +7,7 @@ defmodule Mix.Pleroma do
@cachex_childs ["object", "user"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
+ Pleroma.Config.Holder.save_default()
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do
From 8eecc708efcb405a477d4b2478635f8522c04668 Mon Sep 17 00:00:00 2001
From: Dym Sohin
Date: Thu, 9 Jul 2020 09:03:24 +0000
Subject: [PATCH 074/213] fix wide2x emojis within nicknames
---
priv/static/static-fe/static-fe.css | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/priv/static/static-fe/static-fe.css b/priv/static/static-fe/static-fe.css
index db61ff266..7623b9832 100644
--- a/priv/static/static-fe/static-fe.css
+++ b/priv/static/static-fe/static-fe.css
@@ -181,3 +181,9 @@ img.emoji {
padding: 0;
vertical-align: middle;
}
+
+.display-name img.emoji {
+ width: 16px;
+ height: 16px;
+}
+
From 6465257e1fafd8c4c2ad8fead10ac76bf781fedd Mon Sep 17 00:00:00 2001
From: Dym Sohin
Date: Thu, 9 Jul 2020 09:23:21 +0000
Subject: [PATCH 075/213] missed `:` (colon) before mrf_steal_emoji
---
docs/configuration/cheatsheet.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index f6529b940..d0a57928c 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -155,7 +155,7 @@ config :pleroma, :mrf_user_allowlist, %{
* `:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines
* `:reject` rejects the message entirely
-#### mrf_steal_emoji
+#### :mrf_steal_emoji
* `hosts`: List of hosts to steal emojis from
* `rejected_shortcodes`: Regex-list of shortcodes to reject
* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk
From 8594181597106ebb4ad091990e82293f4e58a933 Mon Sep 17 00:00:00 2001
From: lain
Date: Thu, 9 Jul 2020 09:30:15 +0000
Subject: [PATCH 076/213] Update static-fe.css
---
priv/static/static-fe/static-fe.css | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/priv/static/static-fe/static-fe.css b/priv/static/static-fe/static-fe.css
index 7623b9832..308388abc 100644
--- a/priv/static/static-fe/static-fe.css
+++ b/priv/static/static-fe/static-fe.css
@@ -183,7 +183,6 @@ img.emoji {
}
.display-name img.emoji {
- width: 16px;
- height: 16px;
+ width: 1em;
}
From ab773fa5c946f74b0f42106ba2e72c092566743a Mon Sep 17 00:00:00 2001
From: Dym Sohin
Date: Thu, 9 Jul 2020 09:37:50 +0000
Subject: [PATCH 077/213] [static-fe] limit according to- and within- existing
ruleset
---
priv/static/static-fe/static-fe.css | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/priv/static/static-fe/static-fe.css b/priv/static/static-fe/static-fe.css
index 308388abc..89e9f4877 100644
--- a/priv/static/static-fe/static-fe.css
+++ b/priv/static/static-fe/static-fe.css
@@ -80,6 +80,7 @@ header a:hover, .h-card a:hover {
/* keep emoji from being hilariously huge */
.display-name img {
max-height: 1em;
+ max-width: 1em;
}
.display-name .nickname {
@@ -181,8 +182,3 @@ img.emoji {
padding: 0;
vertical-align: middle;
}
-
-.display-name img.emoji {
- width: 1em;
-}
-
From c2be0da79fcc95399647b292e0b9a4119d3dcdf1 Mon Sep 17 00:00:00 2001
From: eugenijm
Date: Mon, 18 May 2020 22:56:09 +0300
Subject: [PATCH 078/213] Admin API: fix `GET
/api/pleroma/admin/users/:nickname/credentials` returning 404 when getting
the credentials of a remote user while `:instance, :limit_to_local_content`
is set to `:unauthenticated`
---
CHANGELOG.md | 1 +
.../web/admin_api/controllers/admin_api_controller.ex | 10 +++++-----
.../controllers/admin_api_controller_test.exs | 9 +++++++++
3 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92d8c3d8e..c713f1970 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -79,6 +79,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `blob:` urls not being allowed by connect-src CSP
- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
- Rich Media Previews for Twitter links
+- Admin API: fix `GET /api/pleroma/admin/users/:nickname/credentials` returning 404 when getting the credentials of a remote user while `:instance, :limit_to_local_content` is set to `:unauthenticated`
## [Unreleased (patch)]
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index f9545d895..e5f14269a 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -206,8 +206,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
- def user_show(conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
+ def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
conn
|> put_view(AccountView)
|> render("show.json", %{user: user})
@@ -233,11 +233,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> render("index.json", %{activities: activities, as: :activity})
end
- def list_user_statuses(conn, %{"nickname" => nickname} = params) do
+ def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
godmode = params["godmode"] == "true" || params["godmode"] == true
- with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
{_, page_size} = page_params(params)
activities =
@@ -526,7 +526,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@doc "Show a given user's credentials"
def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
conn
|> put_view(AccountView)
|> render("credentials.json", %{user: user, for: admin})
diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs
index 48fb108ec..c2433f23c 100644
--- a/test/web/admin_api/controllers/admin_api_controller_test.exs
+++ b/test/web/admin_api/controllers/admin_api_controller_test.exs
@@ -1514,6 +1514,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
end
end
+ test "gets a remote users when [:instance, :limit_to_local_content] is set to :unauthenticated",
+ %{conn: conn} do
+ clear_config(Pleroma.Config.get([:instance, :limit_to_local_content]), :unauthenticated)
+ user = insert(:user, %{local: false, nickname: "u@peer1.com"})
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
+
+ assert json_response(conn, 200)
+ end
+
describe "GET /users/:nickname/credentials" do
test "gets the user credentials", %{conn: conn} do
user = insert(:user)
From d23804f191eb9e14cfb087863320ae90653c9544 Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Thu, 9 Jul 2020 10:53:51 -0500
Subject: [PATCH 079/213] Use the Pleroma.Config alias
---
lib/mix/tasks/pleroma/instance.ex | 4 +-
lib/pleroma/application.ex | 2 +-
lib/pleroma/emails/admin_email.ex | 2 +-
lib/pleroma/emoji/loader.ex | 2 +-
lib/pleroma/plugs/http_security_plug.ex | 4 +-
lib/pleroma/user.ex | 52 +++++++++----------
.../web/activity_pub/mrf/object_age_policy.ex | 2 +-
.../web/activity_pub/mrf/reject_non_public.ex | 2 +-
.../web/activity_pub/mrf/simple_policy.ex | 2 +-
lib/pleroma/web/common_api/utils.ex | 6 +--
lib/pleroma/web/media_proxy/media_proxy.ex | 2 +-
.../controllers/util_controller.ex | 2 +-
12 files changed, 41 insertions(+), 41 deletions(-)
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index 86409738a..91440b453 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -145,7 +145,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
options,
:uploads_dir,
"What directory should media uploads go in (when using the local uploader)?",
- Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
+ Config.get([Pleroma.Uploaders.Local, :uploads])
)
|> Path.expand()
@@ -154,7 +154,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
options,
:static_dir,
"What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
- Pleroma.Config.get([:instance, :static_dir])
+ Config.get([:instance, :static_dir])
)
|> Path.expand()
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 9615af122..32773d3c9 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -35,7 +35,7 @@ defmodule Pleroma.Application do
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
- Pleroma.Config.Holder.save_default()
+ Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers()
Config.DeprecationWarnings.warn()
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index 55f61024e..c67ba63ad 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Emails.AdminEmail do
alias Pleroma.Config
alias Pleroma.Web.Router.Helpers
- defp instance_config, do: Pleroma.Config.get(:instance)
+ defp instance_config, do: Config.get(:instance)
defp instance_name, do: instance_config()[:name]
defp instance_notify_email do
diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex
index 3de2dc762..03a6bca0b 100644
--- a/lib/pleroma/emoji/loader.ex
+++ b/lib/pleroma/emoji/loader.ex
@@ -108,7 +108,7 @@ defmodule Pleroma.Emoji.Loader do
if File.exists?(emoji_txt) do
load_from_file(emoji_txt, emoji_groups)
else
- extensions = Pleroma.Config.get([:emoji, :pack_extensions])
+ extensions = Config.get([:emoji, :pack_extensions])
Logger.info(
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 472a3ff42..7d65cf078 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -82,14 +82,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
connect_src =
- if Pleroma.Config.get(:env) == :dev do
+ if Config.get(:env) == :dev do
[connect_src, " http://localhost:3035/"]
else
connect_src
end
script_src =
- if Pleroma.Config.get(:env) == :dev do
+ if Config.get(:env) == :dev do
"script-src 'self' 'unsafe-eval'"
else
"script-src 'self'"
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9d1314f81..0078f9831 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -388,8 +388,8 @@ defmodule Pleroma.User do
defp fix_follower_address(params), do: params
def remote_user_changeset(struct \\ %User{local: false}, params) do
- bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
- name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ bio_limit = Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Config.get([:instance, :user_name_length], 100)
name =
case params[:name] do
@@ -448,8 +448,8 @@ defmodule Pleroma.User do
end
def update_changeset(struct, params \\ %{}) do
- bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
- name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ bio_limit = Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Config.get([:instance, :user_name_length], 100)
struct
|> cast(
@@ -618,12 +618,12 @@ defmodule Pleroma.User do
def force_password_reset(user), do: update_password_reset_pending(user, true)
def register_changeset(struct, params \\ %{}, opts \\ []) do
- bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
- name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
+ bio_limit = Config.get([:instance, :user_bio_length], 5000)
+ name_limit = Config.get([:instance, :user_name_length], 100)
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
- Pleroma.Config.get([:instance, :account_activation_required])
+ Config.get([:instance, :account_activation_required])
else
opts[:need_confirmation]
end
@@ -644,7 +644,7 @@ defmodule Pleroma.User do
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
+ |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit)
@@ -659,7 +659,7 @@ defmodule Pleroma.User do
def maybe_validate_required_email(changeset, true), do: changeset
def maybe_validate_required_email(changeset, _) do
- if Pleroma.Config.get([:instance, :account_activation_required]) do
+ if Config.get([:instance, :account_activation_required]) do
validate_required(changeset, [:email])
else
changeset
@@ -679,7 +679,7 @@ defmodule Pleroma.User do
end
defp autofollow_users(user) do
- candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
+ candidates = Config.get([:instance, :autofollowed_nicknames])
autofollowed_users =
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
@@ -706,7 +706,7 @@ defmodule Pleroma.User do
def try_send_confirmation_email(%User{} = user) do
if user.confirmation_pending &&
- Pleroma.Config.get([:instance, :account_activation_required]) do
+ Config.get([:instance, :account_activation_required]) do
user
|> Pleroma.Emails.UserEmail.account_confirmation_email()
|> Pleroma.Emails.Mailer.deliver_async()
@@ -763,7 +763,7 @@ defmodule Pleroma.User do
defdelegate following(user), to: FollowingRelationship
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
- deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+ deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
cond do
followed.deactivated ->
@@ -964,7 +964,7 @@ defmodule Pleroma.User do
end
def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
- restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
+ restrict_to_local = Config.get([:instance, :limit_to_local_content])
cond do
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
@@ -1160,7 +1160,7 @@ defmodule Pleroma.User do
@spec update_follower_count(User.t()) :: {:ok, User.t()}
def update_follower_count(%User{} = user) do
- if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
+ if user.local or !Config.get([:instance, :external_user_synchronization]) do
follower_count = FollowingRelationship.follower_count(user)
user
@@ -1173,7 +1173,7 @@ defmodule Pleroma.User do
@spec update_following_count(User.t()) :: {:ok, User.t()}
def update_following_count(%User{local: false} = user) do
- if Pleroma.Config.get([:instance, :external_user_synchronization]) do
+ if Config.get([:instance, :external_user_synchronization]) do
{:ok, maybe_fetch_follow_information(user)}
else
{:ok, user}
@@ -1260,7 +1260,7 @@ defmodule Pleroma.User do
end
def subscribe(%User{} = subscriber, %User{} = target) do
- deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
+ deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
if blocks?(target, subscriber) and deny_follow_blocked do
{:error, "Could not subscribe: #{target.nickname} is blocking you"}
@@ -1651,7 +1651,7 @@ defmodule Pleroma.User do
Pleroma.HTML.Scrubber.TwitterText
end
- def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
+ def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
@@ -1833,7 +1833,7 @@ defmodule Pleroma.User do
end
defp local_nickname_regex do
- if Pleroma.Config.get([:instance, :extended_nickname_format]) do
+ if Config.get([:instance, :extended_nickname_format]) do
@extended_local_nickname_regex
else
@strict_local_nickname_regex
@@ -1961,8 +1961,8 @@ defmodule Pleroma.User do
def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
# use instance-default
- config = Pleroma.Config.get([:assets, :mascots])
- default_mascot = Pleroma.Config.get([:assets, :default_mascot])
+ config = Config.get([:assets, :mascots])
+ default_mascot = Config.get([:assets, :default_mascot])
mascot = Keyword.get(config, default_mascot)
%{
@@ -2057,7 +2057,7 @@ defmodule Pleroma.User do
def validate_fields(changeset, remote? \\ false) do
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
- limit = Pleroma.Config.get([:instance, limit_name], 0)
+ limit = Config.get([:instance, limit_name], 0)
changeset
|> validate_length(:fields, max: limit)
@@ -2071,8 +2071,8 @@ defmodule Pleroma.User do
end
defp valid_field?(%{"name" => name, "value" => value}) do
- name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
- value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
+ name_limit = Config.get([:instance, :account_field_name_length], 255)
+ value_limit = Config.get([:instance, :account_field_value_length], 255)
is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
String.length(value) <= value_limit
@@ -2082,10 +2082,10 @@ defmodule Pleroma.User do
defp truncate_field(%{"name" => name, "value" => value}) do
{name, _chopped} =
- String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
+ String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
{value, _chopped} =
- String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
+ String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
%{"name" => name, "value" => value}
end
@@ -2140,7 +2140,7 @@ defmodule Pleroma.User do
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
if id not in user.pinned_activities do
- max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
+ max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
params = %{pinned_activities: user.pinned_activities ++ [id]}
user
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 b0ccb63c8..a62914135 100644
--- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex
@@ -98,7 +98,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
@impl true
def describe do
mrf_object_age =
- Pleroma.Config.get(:mrf_object_age)
+ Config.get(:mrf_object_age)
|> Enum.into(%{})
{:ok, %{mrf_object_age: mrf_object_age}}
diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
index 3092f3272..4fd63106d 100644
--- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
+++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex
@@ -47,5 +47,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true
def describe,
- do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
+ do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 9cea6bcf9..70a2ca053 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -155,7 +155,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
%{host: actor_host} = URI.parse(actor)
reject_deletes =
- Pleroma.Config.get([:mrf_simple, :reject_deletes])
+ Config.get([:mrf_simple, :reject_deletes])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 15594125f..9c38b73eb 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -143,7 +143,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
when is_list(options) do
- limits = Pleroma.Config.get([:instance, :poll_limits])
+ limits = Config.get([:instance, :poll_limits])
with :ok <- validate_poll_expiration(expires_in, limits),
:ok <- validate_poll_options_amount(options, limits),
@@ -502,7 +502,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
def make_report_content_html(comment) do
- max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
+ max_size = Config.get([:instance, :max_report_comment_size], 1000)
if String.length(comment) <= max_size do
{:ok, format_input(comment, "text/plain")}
@@ -564,7 +564,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
def validate_character_limit(full_payload, _attachments) do
- limit = Pleroma.Config.get([:instance, :limit])
+ limit = Config.get([:instance, :limit])
length = String.length(full_payload)
if length <= limit do
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex
index 077fabe47..6f35826da 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy.ex
@@ -106,7 +106,7 @@ defmodule Pleroma.Web.MediaProxy do
def build_url(sig_base64, url_base64, filename \\ nil) do
[
- Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
+ Config.get([:media_proxy, :base_url], Web.base_url()),
"proxy",
sig_base64,
url_base64,
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 8314e75b4..f02c4075c 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
def frontend_configurations(conn, _params) do
config =
- Pleroma.Config.get(:frontend_configurations, %{})
+ Config.get(:frontend_configurations, %{})
|> Enum.into(%{})
json(conn, config)
From c2edfd16e063cda117181da66d9b4fec87c121ac Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Thu, 9 Jul 2020 18:59:15 +0300
Subject: [PATCH 080/213] fix for user revoke invite task
---
docs/administration/CLI_tasks/user.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/administration/CLI_tasks/user.md b/docs/administration/CLI_tasks/user.md
index 1e6f4a8b4..3b4c421a7 100644
--- a/docs/administration/CLI_tasks/user.md
+++ b/docs/administration/CLI_tasks/user.md
@@ -57,11 +57,11 @@ mix pleroma.user invites
## Revoke invite
```sh tab="OTP"
- ./bin/pleroma_ctl user revoke_invite
+ ./bin/pleroma_ctl user revoke_invite
```
```sh tab="From Source"
-mix pleroma.user revoke_invite
+mix pleroma.user revoke_invite
```
From d5fcec8315a5f48d94a083b38abcc615834dc532 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Thu, 9 Jul 2020 18:59:48 +0300
Subject: [PATCH 081/213] fix for info after tag/untag user
---
lib/mix/tasks/pleroma/user.ex | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index bca7e87bf..a9370b5e7 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -15,6 +15,8 @@ defmodule Mix.Tasks.Pleroma.User do
@moduledoc File.read!("docs/administration/CLI_tasks/user.md")
def run(["new", nickname, email | rest]) do
+ Application.ensure_all_started(:flake_id)
+
{options, [], []} =
OptionParser.parse(
rest,
@@ -97,6 +99,7 @@ defmodule Mix.Tasks.Pleroma.User do
def run(["rm", nickname]) do
start_pleroma()
+ Application.ensure_all_started(:flake_id)
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
{:ok, delete_data, _} <- Builder.delete(user, user.ap_id),
@@ -232,7 +235,7 @@ defmodule Mix.Tasks.Pleroma.User do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.tag(tags)
- shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
+ shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
else
_ ->
shell_error("Could not change user tags for #{nickname}")
@@ -245,7 +248,7 @@ defmodule Mix.Tasks.Pleroma.User do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.untag(tags)
- shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
+ shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
else
_ ->
shell_error("Could not change user tags for #{nickname}")
@@ -328,6 +331,7 @@ defmodule Mix.Tasks.Pleroma.User do
def run(["delete_activities", nickname]) do
start_pleroma()
+ Application.ensure_all_started(:flake_id)
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
User.delete_user_activities(user)
From 79707e879d3af359be9e1f6ac10717cc9cb72b2c Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Thu, 9 Jul 2020 19:13:16 +0300
Subject: [PATCH 082/213] cleap up
---
lib/mix/pleroma.ex | 20 +++++++++++++++-----
lib/mix/tasks/pleroma/digest.ex | 2 --
lib/mix/tasks/pleroma/email.ex | 1 -
lib/mix/tasks/pleroma/relay.ex | 3 ---
lib/mix/tasks/pleroma/user.ex | 4 ----
5 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
index de16cc52c..9f0bf6ecb 100644
--- a/lib/mix/pleroma.ex
+++ b/lib/mix/pleroma.ex
@@ -3,8 +3,18 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Pleroma do
- @apps [:restarter, :ecto, :ecto_sql, :postgrex, :db_connection, :cachex]
- @cachex_childs ["object", "user"]
+ @apps [
+ :restarter,
+ :ecto,
+ :ecto_sql,
+ :postgrex,
+ :db_connection,
+ :cachex,
+ :flake_id,
+ :swoosh,
+ :timex
+ ]
+ @cachex_children ["object", "user"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Pleroma.Config.Holder.save_default()
@@ -23,15 +33,15 @@ defmodule Mix.Pleroma do
Enum.each(apps, &Application.ensure_all_started/1)
- childs = [
+ children = [
Pleroma.Repo,
{Pleroma.Config.TransferTask, false},
Pleroma.Web.Endpoint
]
- cachex_childs = Enum.map(@cachex_childs, &Pleroma.Application.build_cachex(&1, []))
+ cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
- Supervisor.start_link(childs ++ cachex_childs,
+ Supervisor.start_link(children ++ cachex_children,
strategy: :one_for_one,
name: Pleroma.Supervisor
)
diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex
index 8bde2d4f2..3595f912d 100644
--- a/lib/mix/tasks/pleroma/digest.ex
+++ b/lib/mix/tasks/pleroma/digest.ex
@@ -7,8 +7,6 @@ defmodule Mix.Tasks.Pleroma.Digest do
def run(["test", nickname | opts]) do
Mix.Pleroma.start_pleroma()
- Application.ensure_all_started(:timex)
- Application.ensure_all_started(:swoosh)
user = Pleroma.User.get_by_nickname(nickname)
diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex
index 16fe31431..d3fac6ec8 100644
--- a/lib/mix/tasks/pleroma/email.ex
+++ b/lib/mix/tasks/pleroma/email.ex
@@ -7,7 +7,6 @@ defmodule Mix.Tasks.Pleroma.Email do
def run(["test" | args]) do
Mix.Pleroma.start_pleroma()
- Application.ensure_all_started(:swoosh)
{options, [], []} =
OptionParser.parse(
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index b67d256c3..c3312507e 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -12,7 +12,6 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["follow", target]) do
start_pleroma()
- Application.ensure_all_started(:flake_id)
with {:ok, _activity} <- Relay.follow(target) do
# put this task to sleep to allow the genserver to push out the messages
@@ -24,7 +23,6 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["unfollow", target]) do
start_pleroma()
- Application.ensure_all_started(:flake_id)
with {:ok, _activity} <- Relay.unfollow(target) do
# put this task to sleep to allow the genserver to push out the messages
@@ -36,7 +34,6 @@ defmodule Mix.Tasks.Pleroma.Relay do
def run(["list"]) do
start_pleroma()
- Application.ensure_all_started(:flake_id)
with {:ok, list} <- Relay.list(true) do
list |> Enum.each(&shell_info(&1))
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index a9370b5e7..01824aa18 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -15,8 +15,6 @@ defmodule Mix.Tasks.Pleroma.User do
@moduledoc File.read!("docs/administration/CLI_tasks/user.md")
def run(["new", nickname, email | rest]) do
- Application.ensure_all_started(:flake_id)
-
{options, [], []} =
OptionParser.parse(
rest,
@@ -99,7 +97,6 @@ defmodule Mix.Tasks.Pleroma.User do
def run(["rm", nickname]) do
start_pleroma()
- Application.ensure_all_started(:flake_id)
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
{:ok, delete_data, _} <- Builder.delete(user, user.ap_id),
@@ -331,7 +328,6 @@ defmodule Mix.Tasks.Pleroma.User do
def run(["delete_activities", nickname]) do
start_pleroma()
- Application.ensure_all_started(:flake_id)
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
User.delete_user_activities(user)
From 2b979cc90c4e466a8d0a83706e642b325cc24d0e Mon Sep 17 00:00:00 2001
From: Mark Felder
Date: Thu, 9 Jul 2020 11:55:40 -0500
Subject: [PATCH 083/213] Add AdminFE reports URL to report emails
---
lib/pleroma/emails/admin_email.ex | 2 ++
test/emails/admin_email_test.exs | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex
index c67ba63ad..aa0b2a66b 100644
--- a/lib/pleroma/emails/admin_email.ex
+++ b/lib/pleroma/emails/admin_email.ex
@@ -72,6 +72,8 @@ defmodule Pleroma.Emails.AdminEmail do
Reported Account: #{account.nickname}
#{comment_html}
#{statuses_html}
+
+ View Reports in AdminFE
"""
new()
diff --git a/test/emails/admin_email_test.exs b/test/emails/admin_email_test.exs
index bc871a0a9..9082ae5a7 100644
--- a/test/emails/admin_email_test.exs
+++ b/test/emails/admin_email_test.exs
@@ -31,7 +31,7 @@ defmodule Pleroma.Emails.AdminEmailTest do
account_url
}\">#{account.nickname}
\nComment: Test comment\n
Statuses:\n
\n
\n\n"
+ }\">#{status_url}\n \n\n\n\nView Reports in AdminFE\n"
end
test "it works when the reporter is a remote user without email" do
From cc7153cd828afef1564b58649875b5529c078054 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Thu, 9 Jul 2020 19:07:07 +0200
Subject: [PATCH 084/213] user: Add support for custom emojis in profile fields
---
lib/pleroma/user.ex | 18 ++++++++++----
.../update_credentials_test.exs | 24 +++++++++++++++++++
2 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 9d1314f81..19b361b88 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -527,11 +527,21 @@ defmodule Pleroma.User do
end
defp put_emoji(changeset) do
- bio = get_change(changeset, :bio)
- name = get_change(changeset, :name)
+ emojified_fields = [:bio, :name, :raw_fields]
+
+ if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
+ bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
+ name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
+
+ emoji = Map.merge(bio, name)
+
+ emoji =
+ changeset
+ |> get_field(:raw_fields)
+ |> Enum.reduce(emoji, fn x, acc ->
+ Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
+ end)
- if bio || name do
- emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
put_change(changeset, :emoji, emoji)
else
changeset
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index b55bb76a7..ee5ec9053 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -344,6 +344,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
]
end
+ test "emojis in fields labels", %{conn: conn} do
+ fields = [
+ %{"name" => ":firefox:", "value" => "is best 2hu"},
+ %{"name" => "they wins", "value" => ":blank:"}
+ ]
+
+ account_data =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+ |> json_response_and_validate_schema(200)
+
+ assert account_data["fields"] == [
+ %{"name" => ":firefox:", "value" => "is best 2hu"},
+ %{"name" => "they wins", "value" => ":blank:"}
+ ]
+
+ assert account_data["source"]["fields"] == [
+ %{"name" => ":firefox:", "value" => "is best 2hu"},
+ %{"name" => "they wins", "value" => ":blank:"}
+ ]
+
+ assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = account_data["emojis"]
+ end
+
test "update fields via x-www-form-urlencoded", %{conn: conn} do
fields =
[
From 08211eff22d4aab8ee73dbe16212d2aed1f6789b Mon Sep 17 00:00:00 2001
From: stwf
Date: Wed, 8 Jul 2020 15:56:03 -0400
Subject: [PATCH 085/213] Re-enable the federated tests, increase timeout
---
test/support/cluster.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/support/cluster.ex b/test/support/cluster.ex
index deb37f361..524194cf4 100644
--- a/test/support/cluster.ex
+++ b/test/support/cluster.ex
@@ -97,7 +97,7 @@ defmodule Pleroma.Cluster do
silence_logger_warnings(fn ->
node_configs
|> Enum.map(&Task.async(fn -> start_slave(&1) end))
- |> Enum.map(&Task.await(&1, 60_000))
+ |> Enum.map(&Task.await(&1, 90_000))
end)
end
From 6b9210e886e16f806563f20ac82c0fe56f12a615 Mon Sep 17 00:00:00 2001
From: Angelina Filippova
Date: Fri, 10 Jul 2020 03:07:55 +0300
Subject: [PATCH 086/213] Update type for :groups setting
---
config/description.exs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/config/description.exs b/config/description.exs
index 337f0d307..c2cd40587 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -2527,7 +2527,7 @@ config :pleroma, :config_description, [
%{
key: :styling,
type: :map,
- description: "a map with color settings for email templates.",
+ description: "A map with color settings for email templates.",
suggestions: [
%{
link_color: "#d8a070",
@@ -2633,7 +2633,7 @@ config :pleroma, :config_description, [
},
%{
key: :groups,
- type: {:keyword, :string, {:list, :string}},
+ type: {:keyword, {:list, :string}},
description:
"Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <>
" and the value is the location or array of locations. * can be used as a wildcard.",
From ac9f18de11d3d0583dfae3c6b25c56828357624a Mon Sep 17 00:00:00 2001
From: Angelina Filippova
Date: Fri, 10 Jul 2020 03:32:53 +0300
Subject: [PATCH 087/213] Update type for :replace settings
---
config/description.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/description.exs b/config/description.exs
index c2cd40587..0a0a8e95c 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1601,7 +1601,7 @@ config :pleroma, :config_description, [
},
%{
key: :replace,
- type: [{:tuple, :string, :string}, {:tuple, :regex, :string}],
+ type: {:keyword, :string, :regex},
description:
"A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
From b1b8f5f11a6b74c09490235b30d1b31a54909437 Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Fri, 10 Jul 2020 09:16:53 +0300
Subject: [PATCH 088/213] docs and descriptions for s3 settings
---
config/description.exs | 32 ++++++++++++++++++++++++++++----
docs/configuration/cheatsheet.md | 25 ++++++++++++++++++++++---
2 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/config/description.exs b/config/description.exs
index 03b84bfc8..f461feb04 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -2579,8 +2579,7 @@ config :pleroma, :config_description, [
%{
key: :enabled,
type: :boolean,
- description: "Enables new users admin digest email when `true`",
- suggestions: [false]
+ description: "Enables new users admin digest email when `true`"
}
]
},
@@ -3444,8 +3443,7 @@ config :pleroma, :config_description, [
key: :strict,
type: :boolean,
description:
- "Enables strict input validation (useful in development, not recommended in production)",
- suggestions: [false]
+ "Enables strict input validation (useful in development, not recommended in production)"
}
]
},
@@ -3461,5 +3459,31 @@ config :pleroma, :config_description, [
description: "Allow/disallow displaying and getting instances favicons"
}
]
+ },
+ %{
+ group: :ex_aws,
+ key: :s3,
+ type: :group,
+ descriptions: "S3 service related settings",
+ children: [
+ %{
+ key: :access_key_id,
+ type: :string,
+ description: "S3 access key ID",
+ suggestions: ["AKIAQ8UKHTGIYN7DMWWJ"]
+ },
+ %{
+ key: :secret_access_key,
+ type: :string,
+ description: "Secret access key",
+ suggestions: ["JFGt+fgH1UQ7vLUQjpW+WvjTdV/UNzVxcwn7DkaeFKtBS5LvoXvIiME4NQBsT6ZZ"]
+ },
+ %{
+ key: :host,
+ type: :string,
+ description: "S3 host",
+ suggestions: ["s3.eu-central-1.amazonaws.com"]
+ }
+ ]
}
]
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index d775534b6..1a0603892 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -476,7 +476,6 @@ For each pool, the options are:
* `:timeout` - timeout while `gun` will wait for response
* `:max_overflow` - additional workers if pool is under load
-
## Captcha
### Pleroma.Captcha
@@ -494,7 +493,7 @@ A built-in captcha provider. Enabled by default.
#### Pleroma.Captcha.Kocaptcha
Kocaptcha is a very simple captcha service with a single API endpoint,
-the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint
+the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). The default endpoint
`https://captcha.kotobank.ch` is hosted by the developer.
* `endpoint`: the Kocaptcha endpoint to use.
@@ -502,6 +501,7 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end
## Uploads
### Pleroma.Upload
+
* `uploader`: Which one of the [uploaders](#uploaders) to use.
* `filters`: List of [upload filters](#upload-filters) to use.
* `link_name`: When enabled Pleroma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe`
@@ -514,10 +514,15 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end
`strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
### Uploaders
+
#### Pleroma.Uploaders.Local
+
* `uploads`: Which directory to store the user-uploads in, relative to pleroma’s working directory.
#### Pleroma.Uploaders.S3
+
+Don't forget to configure [Ex AWS S3](#ex-aws-s3-settings)
+
* `bucket`: S3 bucket name.
* `bucket_namespace`: S3 bucket namespace.
* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
@@ -526,6 +531,20 @@ For example, when using CDN to S3 virtual host format, set "".
At this time, write CNAME to CDN in public_endpoint.
* `streaming_enabled`: Enable streaming uploads, when enabled the file will be sent to the server in chunks as it's being read. This may be unsupported by some providers, try disabling this if you have upload problems.
+#### Ex AWS S3 settings
+
+* `access_key_id`: Access key ID
+* `secret_access_key`: Secret access key
+* `host`: S3 host
+
+Example:
+
+```elixir
+config :ex_aws, :s3,
+ access_key_id: "xxxxxxxxxx",
+ secret_access_key: "yyyyyyyyyy",
+ host: "s3.eu-central-1.amazonaws.com"
+```
### Upload filters
@@ -983,7 +1002,7 @@ Restrict access for unauthenticated users to timelines (public and federated), u
* `local`
* `remote`
-Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline).
+Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline).
## Pleroma.Web.ApiSpec.CastAndValidate
From b6688030fad62cc8be32faf928ff6ec418940efc Mon Sep 17 00:00:00 2001
From: Alexander Strizhakov
Date: Fri, 10 Jul 2020 10:35:59 +0300
Subject: [PATCH 089/213] prometheus update for OTP 23
---
mix.exs | 1 +
mix.lock | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/mix.exs b/mix.exs
index e2ab53bde..126aa4709 100644
--- a/mix.exs
+++ b/mix.exs
@@ -178,6 +178,7 @@ defmodule Pleroma.Mixfile do
ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
{:telemetry, "~> 0.3"},
{:poolboy, "~> 1.5"},
+ {:prometheus, "~> 4.6"},
{:prometheus_ex, "~> 3.0"},
{:prometheus_plugs, "~> 1.1"},
{:prometheus_phoenix, "~> 1.3"},
diff --git a/mix.lock b/mix.lock
index 4f2777fa7..30464ddf2 100644
--- a/mix.lock
+++ b/mix.lock
@@ -92,7 +92,7 @@
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4737ce62a31747b4c63c12b20c62307e51bb4fcd730ca0c32c280991e0606c90"},
"pot": {:hex, :pot, "0.10.2", "9895c83bcff8cd22d9f5bc79dfc88a188176b261b618ad70d93faf5c5ca36e67", [:rebar3], [], "hexpm", "ac589a8e296b7802681e93cd0a436faec117ea63e9916709c628df31e17e91e2"},
- "prometheus": {:hex, :prometheus, "4.5.0", "8f4a2246fe0beb50af0f77c5e0a5bb78fe575c34a9655d7f8bc743aad1c6bf76", [:mix, :rebar3], [], "hexpm", "679b5215480fff612b8351f45c839d995a07ce403e42ff02f1c6b20960d41a4e"},
+ "prometheus": {:hex, :prometheus, "4.6.0", "20510f381db1ccab818b4cf2fac5fa6ab5cc91bc364a154399901c001465f46f", [:mix, :rebar3], [], "hexpm", "4905fd2992f8038eccd7aa0cd22f40637ed618c0bed1f75c05aacec15b7545de"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm", "9fd13404a48437e044b288b41f76e64acd9735fb8b0e3809f494811dfa66d0fb"},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "c4d1404ac4e9d3d963da601db2a7d8ea31194f0017057fabf0cfb9bf5a6c8c75"},
From 11c9654a32830b2e36efd42324069c637719555e Mon Sep 17 00:00:00 2001
From: Ben Is
Date: Wed, 8 Jul 2020 22:51:39 +0000
Subject: [PATCH 090/213] Translated using Weblate (Polish)
Currently translated at 66.0% (70 of 106 strings)
Translation: Pleroma/Pleroma backend
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma/pl/
---
priv/gettext/pl/LC_MESSAGES/errors.po | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/priv/gettext/pl/LC_MESSAGES/errors.po b/priv/gettext/pl/LC_MESSAGES/errors.po
index 7bc39c52a..7241d8a0a 100644
--- a/priv/gettext/pl/LC_MESSAGES/errors.po
+++ b/priv/gettext/pl/LC_MESSAGES/errors.po
@@ -3,8 +3,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-13 16:37+0000\n"
-"PO-Revision-Date: 2020-05-16 17:13+0000\n"
-"Last-Translator: Jędrzej Tomaszewski \n"
+"PO-Revision-Date: 2020-07-09 14:40+0000\n"
+"Last-Translator: Ben Is \n"
"Language-Team: Polish \n"
"Language: pl\n"
@@ -50,7 +50,7 @@ msgstr "jest zarezerwowany"
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
-msgstr ""
+msgstr "nie pasuje do potwierdzenia"
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
From b6de9b1987438a3b790c3bc1cd687a7575206e9d Mon Sep 17 00:00:00 2001
From: Ben Is
Date: Wed, 8 Jul 2020 00:00:40 +0000
Subject: [PATCH 091/213] Translated using Weblate (Italian)
Currently translated at 100.0% (106 of 106 strings)
Translation: Pleroma/Pleroma backend
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma/it/
---
priv/gettext/it/LC_MESSAGES/errors.po | 223 +++++++++++++-------------
1 file changed, 114 insertions(+), 109 deletions(-)
diff --git a/priv/gettext/it/LC_MESSAGES/errors.po b/priv/gettext/it/LC_MESSAGES/errors.po
index 726be628b..406a297d1 100644
--- a/priv/gettext/it/LC_MESSAGES/errors.po
+++ b/priv/gettext/it/LC_MESSAGES/errors.po
@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-06-19 14:33+0000\n"
-"PO-Revision-Date: 2020-06-19 20:38+0000\n"
+"PO-Revision-Date: 2020-07-09 14:40+0000\n"
"Last-Translator: Ben Is \n"
"Language-Team: Italian \n"
@@ -29,258 +29,258 @@ msgstr "non può essere nullo"
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
-msgstr ""
+msgstr "è stato già creato"
## From Ecto.Changeset.put_change/3
msgid "is invalid"
-msgstr ""
+msgstr "non è valido"
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
-msgstr ""
+msgstr "è in un formato invalido"
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
-msgstr ""
+msgstr "ha una voce invalida"
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
-msgstr ""
+msgstr "è vietato"
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
-msgstr ""
+msgstr "non corrisponde alla verifica"
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
-msgstr ""
+msgstr "è ancora associato con questa voce"
msgid "are still associated with this entry"
-msgstr ""
+msgstr "sono ancora associati con questa voce"
## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dovrebbe essere %{count} carattere"
+msgstr[1] "dovrebbero essere %{count} caratteri"
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dovrebbe avere %{count} voce"
+msgstr[1] "dovrebbe avere %{count} voci"
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dovrebbe contenere almeno %{count} carattere"
+msgstr[1] "dovrebbe contenere almeno %{count} caratteri"
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dovrebbe avere almeno %{count} voce"
+msgstr[1] "dovrebbe avere almeno %{count} voci"
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dovrebbe avere al massimo %{count} carattere"
+msgstr[1] "dovrebbe avere al massimo %{count} caratteri"
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "dovrebbe avere al massimo %{count} voce"
+msgstr[1] "dovrebbe avere al massimo %{count} voci"
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
-msgstr ""
+msgstr "dev'essere minore di %{number}"
msgid "must be greater than %{number}"
-msgstr ""
+msgstr "dev'essere maggiore di %{number}"
msgid "must be less than or equal to %{number}"
-msgstr ""
+msgstr "dev'essere minore o uguale a %{number}"
msgid "must be greater than or equal to %{number}"
-msgstr ""
+msgstr "dev'essere maggiore o uguale a %{number}"
msgid "must be equal to %{number}"
-msgstr ""
+msgstr "dev'essere uguale a %{number}"
#: lib/pleroma/web/common_api/common_api.ex:421
#, elixir-format
msgid "Account not found"
-msgstr ""
+msgstr "Profilo non trovato"
#: lib/pleroma/web/common_api/common_api.ex:249
#, elixir-format
msgid "Already voted"
-msgstr ""
+msgstr "Hai già votato"
#: lib/pleroma/web/oauth/oauth_controller.ex:360
#, elixir-format
msgid "Bad request"
-msgstr ""
+msgstr "Richiesta invalida"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
#, elixir-format
msgid "Can't delete object"
-msgstr ""
+msgstr "Non puoi eliminare quest'oggetto"
#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:196
#, elixir-format
msgid "Can't delete this post"
-msgstr ""
+msgstr "Non puoi eliminare questo messaggio"
#: lib/pleroma/web/controller_helper.ex:95
#: lib/pleroma/web/controller_helper.ex:101
#, elixir-format
msgid "Can't display this activity"
-msgstr ""
+msgstr "Non puoi vedere questo elemento"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:227
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:254
#, elixir-format
msgid "Can't find user"
-msgstr ""
+msgstr "Non trovo questo utente"
#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:114
#, elixir-format
msgid "Can't get favorites"
-msgstr ""
+msgstr "Non posso ricevere i gradimenti"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:437
#, elixir-format
msgid "Can't like object"
-msgstr ""
+msgstr "Non posso gradire quest'oggetto"
#: lib/pleroma/web/common_api/utils.ex:556
#, elixir-format
msgid "Cannot post an empty status without attachments"
-msgstr ""
+msgstr "Non puoi pubblicare un messaggio vuoto senza allegati"
#: lib/pleroma/web/common_api/utils.ex:504
#, elixir-format
msgid "Comment must be up to %{max_size} characters"
-msgstr ""
+msgstr "I commenti posso al massimo consistere di %{max_size} caratteri"
#: lib/pleroma/config/config_db.ex:222
#, elixir-format
msgid "Config with params %{params} not found"
-msgstr ""
+msgstr "Configurazione con parametri %{max_size} non trovata"
#: lib/pleroma/web/common_api/common_api.ex:95
#, elixir-format
msgid "Could not delete"
-msgstr ""
+msgstr "Non eliminato"
#: lib/pleroma/web/common_api/common_api.ex:141
#, elixir-format
msgid "Could not favorite"
-msgstr ""
+msgstr "Non gradito"
#: lib/pleroma/web/common_api/common_api.ex:370
#, elixir-format
msgid "Could not pin"
-msgstr ""
+msgstr "Non intestato"
#: lib/pleroma/web/common_api/common_api.ex:112
#, elixir-format
msgid "Could not repeat"
-msgstr ""
+msgstr "Non ripetuto"
#: lib/pleroma/web/common_api/common_api.ex:188
#, elixir-format
msgid "Could not unfavorite"
-msgstr ""
+msgstr "Non sgradito"
#: lib/pleroma/web/common_api/common_api.ex:380
#, elixir-format
msgid "Could not unpin"
-msgstr ""
+msgstr "Non de-intestato"
#: lib/pleroma/web/common_api/common_api.ex:126
#, elixir-format
msgid "Could not unrepeat"
-msgstr ""
+msgstr "Non de-ripetuto"
#: lib/pleroma/web/common_api/common_api.ex:428
#: lib/pleroma/web/common_api/common_api.ex:437
#, elixir-format
msgid "Could not update state"
-msgstr ""
+msgstr "Non aggiornato"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
#, elixir-format
msgid "Error."
-msgstr ""
+msgstr "Errore."
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format
msgid "Invalid CAPTCHA"
-msgstr ""
+msgstr "CAPTCHA invalido"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
#: lib/pleroma/web/oauth/oauth_controller.ex:569
#, elixir-format
msgid "Invalid credentials"
-msgstr ""
+msgstr "Credenziali invalide"
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format
msgid "Invalid credentials."
-msgstr ""
+msgstr "Credenziali invalide."
#: lib/pleroma/web/common_api/common_api.ex:265
#, elixir-format
msgid "Invalid indices"
-msgstr ""
+msgstr "Indici invalidi"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
#, elixir-format
msgid "Invalid parameters"
-msgstr ""
+msgstr "Parametri invalidi"
#: lib/pleroma/web/common_api/utils.ex:411
#, elixir-format
msgid "Invalid password."
-msgstr ""
+msgstr "Parola d'ordine invalida."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
#, elixir-format
msgid "Invalid request"
-msgstr ""
+msgstr "Richiesta invalida"
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
#, elixir-format
msgid "Kocaptcha service unavailable"
-msgstr ""
+msgstr "Servizio Kocaptcha non disponibile"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
#, elixir-format
msgid "Missing parameters"
-msgstr ""
+msgstr "Parametri mancanti"
#: lib/pleroma/web/common_api/utils.ex:540
#, elixir-format
msgid "No such conversation"
-msgstr ""
+msgstr "Conversazione inesistente"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
#, elixir-format
msgid "No such permission_group"
-msgstr ""
+msgstr "permission_group non esistente"
#: lib/pleroma/plugs/uploaded_media.ex:74
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format
msgid "Not found"
-msgstr ""
+msgstr "Non trovato"
#: lib/pleroma/web/common_api/common_api.ex:241
#, elixir-format
msgid "Poll's author can't vote"
-msgstr ""
+msgstr "L'autore del sondaggio non può votare"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
@@ -288,215 +288,215 @@ msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#, elixir-format
msgid "Record not found"
-msgstr ""
+msgstr "Voce non trovata"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#, elixir-format
msgid "Something went wrong"
-msgstr ""
+msgstr "C'è stato un problema"
#: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format
msgid "The message visibility must be direct"
-msgstr ""
+msgstr "Il messaggio dev'essere privato"
#: lib/pleroma/web/common_api/utils.ex:566
#, elixir-format
msgid "The status is over the character limit"
-msgstr ""
+msgstr "Il messaggio ha superato la lunghezza massima"
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format
msgid "This resource requires authentication."
-msgstr ""
+msgstr "Accedi per leggere."
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format
msgid "Throttled"
-msgstr ""
+msgstr "Strozzato"
#: lib/pleroma/web/common_api/common_api.ex:266
#, elixir-format
msgid "Too many choices"
-msgstr ""
+msgstr "Troppe alternative"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
#, elixir-format
msgid "Unhandled activity type"
-msgstr ""
+msgstr "Tipo di attività non gestibile"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
#, elixir-format
msgid "You can't revoke your own admin status."
-msgstr ""
+msgstr "Non puoi divestirti da solo."
#: lib/pleroma/web/oauth/oauth_controller.ex:218
#: lib/pleroma/web/oauth/oauth_controller.ex:309
#, elixir-format
msgid "Your account is currently disabled"
-msgstr ""
+msgstr "Il tuo profilo è attualmente disabilitato"
#: lib/pleroma/web/oauth/oauth_controller.ex:180
#: lib/pleroma/web/oauth/oauth_controller.ex:332
#, elixir-format
msgid "Your login is missing a confirmed e-mail address"
-msgstr ""
+msgstr "Devi aggiungere un indirizzo email valido"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
#, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}"
-msgstr ""
+msgstr "non puoi leggere i messaggi privati di %{nickname} come %{as_nickname}"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
#, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}"
-msgstr ""
+msgstr "non puoi aggiornare gli inviati di %{nickname} come %{as_nickname}"
#: lib/pleroma/web/common_api/common_api.ex:388
#, elixir-format
msgid "conversation is already muted"
-msgstr ""
+msgstr "la conversazione è già zittita"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
#, elixir-format
msgid "error"
-msgstr ""
+msgstr "errore"
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
#, elixir-format
msgid "mascots can only be images"
-msgstr ""
+msgstr "le mascotte possono solo essere immagini"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
#, elixir-format
msgid "not found"
-msgstr ""
+msgstr "non trovato"
#: lib/pleroma/web/oauth/oauth_controller.ex:395
#, elixir-format
msgid "Bad OAuth request."
-msgstr ""
+msgstr "Richiesta OAuth malformata."
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format
msgid "CAPTCHA already used"
-msgstr ""
+msgstr "CAPTCHA già utilizzato"
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format
msgid "CAPTCHA expired"
-msgstr ""
+msgstr "CAPTCHA scaduto"
#: lib/pleroma/plugs/uploaded_media.ex:55
#, elixir-format
msgid "Failed"
-msgstr ""
+msgstr "Fallito"
#: lib/pleroma/web/oauth/oauth_controller.ex:411
#, elixir-format
msgid "Failed to authenticate: %{message}."
-msgstr ""
+msgstr "Autenticazione fallita per: %{message}."
#: lib/pleroma/web/oauth/oauth_controller.ex:442
#, elixir-format
msgid "Failed to set up user account."
-msgstr ""
+msgstr "Profilo utente non creato."
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format
msgid "Insufficient permissions: %{permissions}."
-msgstr ""
+msgstr "Permessi insufficienti: %{permissions}."
#: lib/pleroma/plugs/uploaded_media.ex:94
#, elixir-format
msgid "Internal Error"
-msgstr ""
+msgstr "Errore interno"
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format
msgid "Invalid Username/Password"
-msgstr ""
+msgstr "Nome utente/parola d'ordine invalidi"
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format
msgid "Invalid answer data"
-msgstr ""
+msgstr "Risposta malformata"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
#, elixir-format
msgid "Nodeinfo schema version not handled"
-msgstr ""
+msgstr "Versione schema nodeinfo non compatibile"
#: lib/pleroma/web/oauth/oauth_controller.ex:169
#, elixir-format
msgid "This action is outside the authorized scopes"
-msgstr ""
+msgstr "Quest'azione non è consentita in questa visibilità"
#: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format
msgid "Unknown error, please check the details and try again."
-msgstr ""
+msgstr "Errore sconosciuto, controlla i dettagli e riprova."
#: lib/pleroma/web/oauth/oauth_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:155
#, elixir-format
msgid "Unlisted redirect_uri."
-msgstr ""
+msgstr "redirect_uri nascosto."
#: lib/pleroma/web/oauth/oauth_controller.ex:391
#, elixir-format
msgid "Unsupported OAuth provider: %{provider}."
-msgstr ""
+msgstr "Gestore OAuth non supportato: %{provider}."
#: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format
msgid "Uploader callback timeout"
-msgstr ""
+msgstr "Callback caricatmento scaduta"
#: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format
msgid "bad request"
-msgstr ""
+msgstr "richiesta malformata"
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format
msgid "CAPTCHA Error"
-msgstr ""
+msgstr "Errore CAPTCHA"
#: lib/pleroma/web/common_api/common_api.ex:200
#, elixir-format
msgid "Could not add reaction emoji"
-msgstr ""
+msgstr "Reazione emoji non riuscita"
#: lib/pleroma/web/common_api/common_api.ex:211
#, elixir-format
msgid "Could not remove reaction emoji"
-msgstr ""
+msgstr "Rimozione reazione non riuscita"
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
-msgstr ""
+msgstr "CAPTCHA invalido (Parametro mancante: %{name})"
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format
msgid "List not found"
-msgstr ""
+msgstr "Lista non trovata"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
#, elixir-format
msgid "Missing parameter: %{name}"
-msgstr ""
+msgstr "Parametro mancante: %{name}"
#: lib/pleroma/web/oauth/oauth_controller.ex:207
#: lib/pleroma/web/oauth/oauth_controller.ex:322
#, elixir-format
msgid "Password reset is required"
-msgstr ""
+msgstr "Necessario reimpostare parola d'ordine"
#: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
@@ -528,53 +528,58 @@ msgstr ""
#, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
+"Sicurezza violata: il controllo autorizzazioni di OAuth non è stato svolto "
+"né saltato."
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format
msgid "Two-factor authentication enabled, you must use a access token."
msgstr ""
+"Autenticazione bifattoriale abilitata, devi utilizzare una chiave d'accesso."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
#, elixir-format
msgid "Unexpected error occurred while adding file to pack."
-msgstr ""
+msgstr "Errore inaspettato durante l'aggiunta del file al pacchetto."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
#, elixir-format
msgid "Unexpected error occurred while creating pack."
-msgstr ""
+msgstr "Errore inaspettato durante la creazione del pacchetto."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
#, elixir-format
msgid "Unexpected error occurred while removing file from pack."
-msgstr ""
+msgstr "Errore inaspettato durante la rimozione del file dal pacchetto."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
#, elixir-format
msgid "Unexpected error occurred while updating file in pack."
-msgstr ""
+msgstr "Errore inaspettato durante l'aggiornamento del file nel pacchetto."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
#, elixir-format
msgid "Unexpected error occurred while updating pack metadata."
-msgstr ""
+msgstr "Errore inaspettato durante l'aggiornamento dei metadati del pacchetto."
#: lib/pleroma/plugs/user_is_admin_plug.ex:40
#, elixir-format
msgid "User is not an admin or OAuth admin scope is not granted."
msgstr ""
+"L'utente non è un amministratore o non ha ricevuto questa autorizzazione "
+"OAuth."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format
msgid "Web push subscription is disabled on this Pleroma instance"
-msgstr ""
+msgstr "Gli aggiornamenti web push non sono disponibili in questa stanza"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502
#, elixir-format
msgid "You can't revoke your own admin/moderator status."
-msgstr ""
+msgstr "Non puoi divestire te stesso."
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
#, elixir-format
msgid "authorization required for timeline view"
-msgstr ""
+msgstr "autorizzazione richiesta per vedere la sequenza"
From 328062308a2cfbed151a63d8166853a1965c59db Mon Sep 17 00:00:00 2001
From: lain
Date: Fri, 10 Jul 2020 11:41:10 +0200
Subject: [PATCH 092/213] Update frontend
---
priv/static/index.html | 2 +-
.../css/app.493b9b5acee37ba97824.css.map | 1 -
...97824.css => app.77b1644622e3bae24b6b.css} | 5 +-
.../css/app.77b1644622e3bae24b6b.css.map | 1 +
.../static/font/fontello.1594134783339.woff | Bin 14772 -> 0 bytes
.../static/font/fontello.1594134783339.woff2 | Bin 12416 -> 0 bytes
...4783339.eot => fontello.1594374054351.eot} | Bin 24332 -> 24524 bytes
...4783339.svg => fontello.1594374054351.svg} | 2 +
...4783339.ttf => fontello.1594374054351.ttf} | Bin 24164 -> 24356 bytes
.../static/font/fontello.1594374054351.woff | Bin 0 -> 14912 bytes
.../static/font/fontello.1594374054351.woff2 | Bin 0 -> 12540 bytes
priv/static/static/fontello.1594374054351.css | 158 ++++++++++++++++++
priv/static/static/fontello.json | 6 +
...cd678d2f.js => 10.2823375ec309b971aaea.js} | 4 +-
....js.map => 10.2823375ec309b971aaea.js.map} | 2 +-
...caef9adb.js => 11.2cb4b0f72a4654070a58.js} | 4 +-
....js.map => 11.2cb4b0f72a4654070a58.js.map} | 2 +-
...3f0c6e1f.js => 12.500b3e4676dd47599a58.js} | 4 +-
....js.map => 12.500b3e4676dd47599a58.js.map} | 2 +-
...42678085.js => 13.3ef79a2643680080d28f.js} | 4 +-
....js.map => 13.3ef79a2643680080d28f.js.map} | 2 +-
...d2a4cfbc.js => 14.b7f6eb3ea71d2ac2bb41.js} | 4 +-
.../static/js/14.b7f6eb3ea71d2ac2bb41.js.map | 1 +
.../static/js/14.cc092634462fd2a4cfbc.js.map | 1 -
...26398e00.js => 15.d814a29a970070494722.js} | 4 +-
.../static/js/15.d814a29a970070494722.js.map | 1 +
.../static/js/15.e9ddc5dfd38426398e00.js.map | 1 -
...3264469e.js => 16.017fa510b293035ac370.js} | 4 +-
.../static/js/16.017fa510b293035ac370.js.map | 1 +
.../static/js/16.476e7809b8593264469e.js.map | 1 -
.../static/js/17.acbe4c09f05ae56c76a2.js.map | 1 -
...e56c76a2.js => 17.c63932b65417ee7346a3.js} | 4 +-
.../static/js/17.c63932b65417ee7346a3.js.map | 1 +
.../static/js/18.a8ccd7f2a47c5c94b3b9.js.map | 1 -
...5c94b3b9.js => 18.fd12f9746a55aa24a8b7.js} | 4 +-
.../static/js/18.fd12f9746a55aa24a8b7.js.map | 1 +
...d5e45872.js => 19.3adebd64964c92700074.js} | 4 +-
.../static/js/19.3adebd64964c92700074.js.map | 1 +
.../static/js/19.5894e9c12b4fd5e45872.js.map | 1 -
.../static/js/2.d81ca020d6885c6c3b03.js | 2 +
.../static/js/2.d81ca020d6885c6c3b03.js.map | 1 +
.../static/js/2.f8dee9318a6f84ea92c3.js | 2 -
.../static/js/2.f8dee9318a6f84ea92c3.js.map | 1 -
.../static/js/20.43b5b27b0f68474f3b72.js.map | 1 -
...474f3b72.js => 20.e0c3ad29d59470506c04.js} | 4 +-
.../static/js/20.e0c3ad29d59470506c04.js.map | 1 +
.../static/js/21.72b45b01be9d0f4c62ce.js.map | 1 -
...0f4c62ce.js => 21.849ecc09a1d58bdc64c6.js} | 4 +-
.../static/js/21.849ecc09a1d58bdc64c6.js.map | 1 +
.../static/js/22.26f13a22ad57a0d14670.js.map | 1 -
...a0d14670.js => 22.8782f133c9f66d3f2bbe.js} | 4 +-
.../static/js/22.8782f133c9f66d3f2bbe.js.map | 1 +
...a806f887.js => 23.2653bf91bc77c2ed0160.js} | 4 +-
.../static/js/23.2653bf91bc77c2ed0160.js.map | 1 +
.../static/js/23.91a60b775352a806f887.js.map | 1 -
.../static/js/24.c8d8438aac954d4707ac.js.map | 1 -
...4d4707ac.js => 24.f931d864a2297d880a9a.js} | 4 +-
.../static/js/24.f931d864a2297d880a9a.js.map | 1 +
.../static/js/25.79ac9e020d571b67f02a.js.map | 1 -
...1b67f02a.js => 25.886acc9ba83c64659279.js} | 4 +-
.../static/js/25.886acc9ba83c64659279.js.map | 1 +
.../static/js/26.3af8f54349f672f2c7c8.js.map | 1 -
...72f2c7c8.js => 26.e15b1645079c72c60586.js} | 4 +-
.../static/js/26.e15b1645079c72c60586.js.map | 1 +
.../static/js/27.51287d408313da67b0b8.js.map | 1 -
...da67b0b8.js => 27.7b41e5953f74af7fddd1.js} | 4 +-
.../static/js/27.7b41e5953f74af7fddd1.js.map | 1 +
...8a81332d.js => 28.4f39e562aaceaa01e883.js} | 4 +-
.../static/js/28.4f39e562aaceaa01e883.js.map | 1 +
.../static/js/28.be5118beb1098a81332d.js.map | 1 -
.../static/js/29.084f6fb0987d3862d410.js.map | 1 -
...3862d410.js => 29.137e2a68b558eed58152.js} | 4 +-
.../static/js/29.137e2a68b558eed58152.js.map | 1 +
...40e12e850.js => 3.56898c1005d9ba1b8d4a.js} | 8 +-
...0.js.map => 3.56898c1005d9ba1b8d4a.js.map} | 2 +-
.../static/js/30.6e6d63411def2e175d11.js.map | 1 -
...2e175d11.js => 30.73e09f3b43617410dec7.js} | 4 +-
.../static/js/30.73e09f3b43617410dec7.js.map | 1 +
...90b36e3f5.js => 4.2d3bef896b463484e6eb.js} | 4 +-
...5.js.map => 4.2d3bef896b463484e6eb.js.map} | 2 +-
...2d54ffdc9.js => 5.2b4a2787bacdd3d910db.js} | 4 +-
...9.js.map => 5.2b4a2787bacdd3d910db.js.map} | 2 +-
...44f0ba121.js => 6.9c94bc0cc78979694cf4.js} | 4 +-
...1.js.map => 6.9c94bc0cc78979694cf4.js.map} | 2 +-
...f668601a6.js => 7.b4ac57fd946a3a189047.js} | 4 +-
...6.js.map => 7.b4ac57fd946a3a189047.js.map} | 2 +-
...4a6b96a29.js => 8.e03e32ca713d01db0433.js} | 4 +-
...9.js.map => 8.e03e32ca713d01db0433.js.map} | 2 +-
...aee67515e.js => 9.72d903ca8e0c5a532b87.js} | 4 +-
.../static/js/9.72d903ca8e0c5a532b87.js.map | 1 +
.../static/js/9.ef4eb9703f9aee67515e.js.map | 1 -
.../static/js/app.1e68e208590653dab5aa.js | 2 +
.../static/js/app.1e68e208590653dab5aa.js.map | 1 +
.../static/js/app.53001fa190f37cf2743e.js | 2 -
.../static/js/app.53001fa190f37cf2743e.js.map | 1 -
...js => vendors~app.247dc52c7abe6a0dab87.js} | 16 +-
.../vendors~app.247dc52c7abe6a0dab87.js.map | 1 +
.../vendors~app.8837fb59589d1dd6acda.js.map | 1 -
priv/static/static/terms-of-service.html | 2 +-
priv/static/sw-pleroma.js | 4 +-
priv/static/sw-pleroma.js.map | 2 +-
101 files changed, 277 insertions(+), 108 deletions(-)
delete mode 100644 priv/static/static/css/app.493b9b5acee37ba97824.css.map
rename priv/static/static/css/{app.493b9b5acee37ba97824.css => app.77b1644622e3bae24b6b.css} (98%)
create mode 100644 priv/static/static/css/app.77b1644622e3bae24b6b.css.map
delete mode 100644 priv/static/static/font/fontello.1594134783339.woff
delete mode 100644 priv/static/static/font/fontello.1594134783339.woff2
rename priv/static/static/font/{fontello.1594134783339.eot => fontello.1594374054351.eot} (89%)
rename priv/static/static/font/{fontello.1594134783339.svg => fontello.1594374054351.svg} (98%)
rename priv/static/static/font/{fontello.1594134783339.ttf => fontello.1594374054351.ttf} (89%)
create mode 100644 priv/static/static/font/fontello.1594374054351.woff
create mode 100644 priv/static/static/font/fontello.1594374054351.woff2
create mode 100644 priv/static/static/fontello.1594374054351.css
rename priv/static/static/js/{10.4a22c77e34edcd678d2f.js => 10.2823375ec309b971aaea.js} (99%)
rename priv/static/static/js/{10.4a22c77e34edcd678d2f.js.map => 10.2823375ec309b971aaea.js.map} (56%)
rename priv/static/static/js/{11.787aa24e4fd5caef9adb.js => 11.2cb4b0f72a4654070a58.js} (99%)
rename priv/static/static/js/{11.787aa24e4fd5caef9adb.js.map => 11.2cb4b0f72a4654070a58.js.map} (56%)
rename priv/static/static/js/{12.35a510cf14233f0c6e1f.js => 12.500b3e4676dd47599a58.js} (99%)
rename priv/static/static/js/{12.35a510cf14233f0c6e1f.js.map => 12.500b3e4676dd47599a58.js.map} (56%)
rename priv/static/static/js/{13.7931a609d62a42678085.js => 13.3ef79a2643680080d28f.js} (99%)
rename priv/static/static/js/{13.7931a609d62a42678085.js.map => 13.3ef79a2643680080d28f.js.map} (56%)
rename priv/static/static/js/{14.cc092634462fd2a4cfbc.js => 14.b7f6eb3ea71d2ac2bb41.js} (99%)
create mode 100644 priv/static/static/js/14.b7f6eb3ea71d2ac2bb41.js.map
delete mode 100644 priv/static/static/js/14.cc092634462fd2a4cfbc.js.map
rename priv/static/static/js/{15.e9ddc5dfd38426398e00.js => 15.d814a29a970070494722.js} (98%)
create mode 100644 priv/static/static/js/15.d814a29a970070494722.js.map
delete mode 100644 priv/static/static/js/15.e9ddc5dfd38426398e00.js.map
rename priv/static/static/js/{16.476e7809b8593264469e.js => 16.017fa510b293035ac370.js} (99%)
create mode 100644 priv/static/static/js/16.017fa510b293035ac370.js.map
delete mode 100644 priv/static/static/js/16.476e7809b8593264469e.js.map
delete mode 100644 priv/static/static/js/17.acbe4c09f05ae56c76a2.js.map
rename priv/static/static/js/{17.acbe4c09f05ae56c76a2.js => 17.c63932b65417ee7346a3.js} (94%)
create mode 100644 priv/static/static/js/17.c63932b65417ee7346a3.js.map
delete mode 100644 priv/static/static/js/18.a8ccd7f2a47c5c94b3b9.js.map
rename priv/static/static/js/{18.a8ccd7f2a47c5c94b3b9.js => 18.fd12f9746a55aa24a8b7.js} (99%)
create mode 100644 priv/static/static/js/18.fd12f9746a55aa24a8b7.js.map
rename priv/static/static/js/{19.5894e9c12b4fd5e45872.js => 19.3adebd64964c92700074.js} (99%)
create mode 100644 priv/static/static/js/19.3adebd64964c92700074.js.map
delete mode 100644 priv/static/static/js/19.5894e9c12b4fd5e45872.js.map
create mode 100644 priv/static/static/js/2.d81ca020d6885c6c3b03.js
create mode 100644 priv/static/static/js/2.d81ca020d6885c6c3b03.js.map
delete mode 100644 priv/static/static/js/2.f8dee9318a6f84ea92c3.js
delete mode 100644 priv/static/static/js/2.f8dee9318a6f84ea92c3.js.map
delete mode 100644 priv/static/static/js/20.43b5b27b0f68474f3b72.js.map
rename priv/static/static/js/{20.43b5b27b0f68474f3b72.js => 20.e0c3ad29d59470506c04.js} (99%)
create mode 100644 priv/static/static/js/20.e0c3ad29d59470506c04.js.map
delete mode 100644 priv/static/static/js/21.72b45b01be9d0f4c62ce.js.map
rename priv/static/static/js/{21.72b45b01be9d0f4c62ce.js => 21.849ecc09a1d58bdc64c6.js} (99%)
create mode 100644 priv/static/static/js/21.849ecc09a1d58bdc64c6.js.map
delete mode 100644 priv/static/static/js/22.26f13a22ad57a0d14670.js.map
rename priv/static/static/js/{22.26f13a22ad57a0d14670.js => 22.8782f133c9f66d3f2bbe.js} (99%)
create mode 100644 priv/static/static/js/22.8782f133c9f66d3f2bbe.js.map
rename priv/static/static/js/{23.91a60b775352a806f887.js => 23.2653bf91bc77c2ed0160.js} (99%)
create mode 100644 priv/static/static/js/23.2653bf91bc77c2ed0160.js.map
delete mode 100644 priv/static/static/js/23.91a60b775352a806f887.js.map
delete mode 100644 priv/static/static/js/24.c8d8438aac954d4707ac.js.map
rename priv/static/static/js/{24.c8d8438aac954d4707ac.js => 24.f931d864a2297d880a9a.js} (99%)
create mode 100644 priv/static/static/js/24.f931d864a2297d880a9a.js.map
delete mode 100644 priv/static/static/js/25.79ac9e020d571b67f02a.js.map
rename priv/static/static/js/{25.79ac9e020d571b67f02a.js => 25.886acc9ba83c64659279.js} (99%)
create mode 100644 priv/static/static/js/25.886acc9ba83c64659279.js.map
delete mode 100644 priv/static/static/js/26.3af8f54349f672f2c7c8.js.map
rename priv/static/static/js/{26.3af8f54349f672f2c7c8.js => 26.e15b1645079c72c60586.js} (99%)
create mode 100644 priv/static/static/js/26.e15b1645079c72c60586.js.map
delete mode 100644 priv/static/static/js/27.51287d408313da67b0b8.js.map
rename priv/static/static/js/{27.51287d408313da67b0b8.js => 27.7b41e5953f74af7fddd1.js} (94%)
create mode 100644 priv/static/static/js/27.7b41e5953f74af7fddd1.js.map
rename priv/static/static/js/{28.be5118beb1098a81332d.js => 28.4f39e562aaceaa01e883.js} (99%)
create mode 100644 priv/static/static/js/28.4f39e562aaceaa01e883.js.map
delete mode 100644 priv/static/static/js/28.be5118beb1098a81332d.js.map
delete mode 100644 priv/static/static/js/29.084f6fb0987d3862d410.js.map
rename priv/static/static/js/{29.084f6fb0987d3862d410.js => 29.137e2a68b558eed58152.js} (99%)
create mode 100644 priv/static/static/js/29.137e2a68b558eed58152.js.map
rename priv/static/static/js/{3.e1f7d368d5840e12e850.js => 3.56898c1005d9ba1b8d4a.js} (99%)
rename priv/static/static/js/{3.e1f7d368d5840e12e850.js.map => 3.56898c1005d9ba1b8d4a.js.map} (99%)
delete mode 100644 priv/static/static/js/30.6e6d63411def2e175d11.js.map
rename priv/static/static/js/{30.6e6d63411def2e175d11.js => 30.73e09f3b43617410dec7.js} (99%)
create mode 100644 priv/static/static/js/30.73e09f3b43617410dec7.js.map
rename priv/static/static/js/{4.c3f92d0b6ff90b36e3f5.js => 4.2d3bef896b463484e6eb.js} (77%)
rename priv/static/static/js/{4.c3f92d0b6ff90b36e3f5.js.map => 4.2d3bef896b463484e6eb.js.map} (99%)
rename priv/static/static/js/{5.d30e50cd5c52d54ffdc9.js => 5.2b4a2787bacdd3d910db.js} (98%)
rename priv/static/static/js/{5.d30e50cd5c52d54ffdc9.js.map => 5.2b4a2787bacdd3d910db.js.map} (57%)
rename priv/static/static/js/{6.fa6d5c2d85d44f0ba121.js => 6.9c94bc0cc78979694cf4.js} (98%)
rename priv/static/static/js/{6.fa6d5c2d85d44f0ba121.js.map => 6.9c94bc0cc78979694cf4.js.map} (57%)
rename priv/static/static/js/{7.d558a086622f668601a6.js => 7.b4ac57fd946a3a189047.js} (99%)
rename priv/static/static/js/{7.d558a086622f668601a6.js.map => 7.b4ac57fd946a3a189047.js.map} (57%)
rename priv/static/static/js/{8.615136ce6c34a6b96a29.js => 8.e03e32ca713d01db0433.js} (99%)
rename priv/static/static/js/{8.615136ce6c34a6b96a29.js.map => 8.e03e32ca713d01db0433.js.map} (57%)
rename priv/static/static/js/{9.ef4eb9703f9aee67515e.js => 9.72d903ca8e0c5a532b87.js} (99%)
create mode 100644 priv/static/static/js/9.72d903ca8e0c5a532b87.js.map
delete mode 100644 priv/static/static/js/9.ef4eb9703f9aee67515e.js.map
create mode 100644 priv/static/static/js/app.1e68e208590653dab5aa.js
create mode 100644 priv/static/static/js/app.1e68e208590653dab5aa.js.map
delete mode 100644 priv/static/static/js/app.53001fa190f37cf2743e.js
delete mode 100644 priv/static/static/js/app.53001fa190f37cf2743e.js.map
rename priv/static/static/js/{vendors~app.8837fb59589d1dd6acda.js => vendors~app.247dc52c7abe6a0dab87.js} (62%)
create mode 100644 priv/static/static/js/vendors~app.247dc52c7abe6a0dab87.js.map
delete mode 100644 priv/static/static/js/vendors~app.8837fb59589d1dd6acda.js.map
diff --git a/priv/static/index.html b/priv/static/index.html
index 3ef4baa26..80820166a 100644
--- a/priv/static/index.html
+++ b/priv/static/index.html
@@ -1 +1 @@
-Pleroma
\ No newline at end of file
+Pleroma
\ No newline at end of file
diff --git a/priv/static/static/css/app.493b9b5acee37ba97824.css.map b/priv/static/static/css/app.493b9b5acee37ba97824.css.map
deleted file mode 100644
index 91399d605..000000000
--- a/priv/static/static/css/app.493b9b5acee37ba97824.css.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.493b9b5acee37ba97824.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n font-size: 2em;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 1em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}"],"sourceRoot":""}
\ No newline at end of file
diff --git a/priv/static/static/css/app.493b9b5acee37ba97824.css b/priv/static/static/css/app.77b1644622e3bae24b6b.css
similarity index 98%
rename from priv/static/static/css/app.493b9b5acee37ba97824.css
rename to priv/static/static/css/app.77b1644622e3bae24b6b.css
index f30033af6..8038882c0 100644
--- a/priv/static/static/css/app.493b9b5acee37ba97824.css
+++ b/priv/static/static/css/app.77b1644622e3bae24b6b.css
@@ -239,5 +239,8 @@
.with-load-more-footer .error {
font-size: 14px;
}
+.with-load-more-footer a {
+ cursor: pointer;
+}
-/*# sourceMappingURL=app.493b9b5acee37ba97824.css.map*/
\ No newline at end of file
+/*# sourceMappingURL=app.77b1644622e3bae24b6b.css.map*/
\ No newline at end of file
diff --git a/priv/static/static/css/app.77b1644622e3bae24b6b.css.map b/priv/static/static/css/app.77b1644622e3bae24b6b.css.map
new file mode 100644
index 000000000..4b042ef35
--- /dev/null
+++ b/priv/static/static/css/app.77b1644622e3bae24b6b.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_load_more/with_load_more.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACtOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.77b1644622e3bae24b6b.css","sourcesContent":[".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n}\n.tab-switcher .tab-icon {\n font-size: 2em;\n display: block;\n}\n.tab-switcher.top-tabs {\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.top-tabs > .tabs {\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n -ms-flex-direction: row;\n flex-direction: row;\n}\n.tab-switcher.top-tabs > .tabs::after, .tab-switcher.top-tabs > .tabs::before {\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper {\n height: 28px;\n}\n.tab-switcher.top-tabs > .tabs .tab-wrapper:not(.active)::after {\n left: 0;\n right: 0;\n bottom: 0;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher.top-tabs > .tabs .tab {\n width: 100%;\n min-width: 1px;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding-bottom: 99px;\n margin-bottom: -93px;\n}\n.tab-switcher.top-tabs .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n}\n.tab-switcher.side-tabs {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs {\n overflow-x: auto;\n }\n}\n.tab-switcher.side-tabs > .contents {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher.side-tabs > .tabs {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n overflow-y: auto;\n overflow-x: hidden;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher.side-tabs > .tabs::after, .tab-switcher.side-tabs > .tabs::before {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n -ms-flex-preferred-size: 0.5em;\n flex-basis: 0.5em;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs::after {\n -ms-flex-positive: 1;\n flex-grow: 1;\n}\n.tab-switcher.side-tabs > .tabs::before {\n -ms-flex-positive: 0;\n flex-grow: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 10em;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab-wrapper {\n min-width: 1em;\n }\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:not(.active)::after {\n top: 0;\n right: 0;\n bottom: 0;\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper::before {\n -ms-flex: 0 0 6px;\n flex: 0 0 6px;\n content: \"\";\n border-right: 1px solid;\n border-right-color: #222;\n border-right-color: var(--border, #222);\n}\n.tab-switcher.side-tabs > .tabs .tab-wrapper:last-child .tab {\n margin-bottom: 0;\n}\n.tab-switcher.side-tabs > .tabs .tab {\n -ms-flex: 1;\n flex: 1;\n box-sizing: content-box;\n min-width: 10em;\n min-width: 1px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding-left: 1em;\n padding-right: calc(1em + 200px);\n margin-right: -200px;\n margin-left: 1em;\n}\n@media all and (max-width: 800px) {\n .tab-switcher.side-tabs > .tabs .tab {\n padding-left: 0.25em;\n padding-right: calc(.25em + 200px);\n margin-right: calc(.25em - 200px);\n margin-left: 0.25em;\n }\n .tab-switcher.side-tabs > .tabs .tab .text {\n display: none;\n }\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents .full-height:not(.hidden) {\n height: 100%;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents .full-height:not(.hidden) > *:not(.mobile-label) {\n -ms-flex: 1;\n flex: 1;\n}\n.tab-switcher .contents.scrollable-tabs {\n overflow-y: auto;\n}\n.tab-switcher .tab {\n position: relative;\n white-space: nowrap;\n padding: 6px 1em;\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tab, .tab-switcher .tab:active .tab-icon {\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n}\n.tab-switcher .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n.tab-switcher .tab-wrapper {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n z-index: 7;\n}\n.tab-switcher .mobile-label {\n padding-left: 0.3em;\n padding-bottom: 0.25em;\n margin-top: 0.5em;\n margin-left: 0.2em;\n margin-bottom: 0.25em;\n border-bottom: 1px solid var(--border, #222);\n}\n@media all and (min-width: 800px) {\n .tab-switcher .mobile-label {\n display: none;\n }\n}",".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}\n.with-load-more-footer a {\n cursor: pointer;\n}"],"sourceRoot":""}
\ No newline at end of file
diff --git a/priv/static/static/font/fontello.1594134783339.woff b/priv/static/static/font/fontello.1594134783339.woff
deleted file mode 100644
index 89a337131b11f8cdb6513888c020e317b37c4187..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 14772
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
y5+jp*vk>WyiTSj?TFH3Z}TBO?0~N
zjNh*%pAKk6r=we>`J?0o(nHRUn{C?#&KH0t2`FiFwK`fN{Kvw8@?nbSxoASOEJTxE
zB%kDR_w;QQRtMhE#0DKIbaXa5@^z(N!1jq247(kDB71mb2B>h6H{5WzTUVci>&cG*
z+^X2w-Q&J=!3x-G$j^#ht>UINjgmJ`NK>qyjY+?l^JF_1?FMTBfAEz~8bs^cA+Q1p
z7-4{bAiq%><%W{`A&?BojZRvQSR7w8a0Ab~$aRTq5vPjDyGG^$9=OfD!N$
zNFy~VfNJN@RX_%?JEnk4(t8TXB7LNQY|=ReoFZLT0Oi}=P(ThTl}rYddv{Y|0H^8W
zs{-;!_Z5&2>``N6KsDH-n#$k|saXMqq*PlOoFy$$KoMz+0?v_cC_vsv2g~3*@Ff?A
zdN*TOeTBRH8yRsk5$&!*GzR$+%vK8xX-LE>zILeChvnT+PCEC
zZ3->=4lVDeU%|(Wt@*zyO_b*|tMtD7IbRzHNQ1UnQ@Bq*8Ic*W5^0R=kDQOvM~y@m
zMz6%Q#jM4ih+T+_jq}94GBg?%`_fUnf9b0$0fgodCPen##>F%Znj9CQEi;+}x@&0xTV;0mb%0t8H
zuF0l%!09Q*)AFULyc%eI3iPcgQ_@*!LRpjI%?Xy~kouwhvvSeZd_Mu5Rlpiw*<1xa^`0yFWkabg-{*a0t}V=k
zeBqkTA24c5%;QZn6%
s%ewx$h5E+&nA7CE(BN)(%K~fze|}q+QOgQDx-
+
+
diff --git a/priv/static/static/font/fontello.1594134783339.ttf b/priv/static/static/font/fontello.1594374054351.ttf
similarity index 89%
rename from priv/static/static/font/fontello.1594134783339.ttf
rename to priv/static/static/font/fontello.1594374054351.ttf
index 458e88f9e623bcc6d289dc965512734c271cbd3e..be55bef817f9dfd9bd2048d7f813b615eb18b076 100644
GIT binary patch
delta 921
zcmZvaUr19?9LK-s-c3u*%0`xMSck2R)mhOZfC^>GhnSD
zKO=ND3!CONO5QL&_S42jJXOqbstt@5gEfH<_R>kiXnGrDtbhVKXdy$GPv_ZS1Ic|5
zTmQdy7yoIVVQxTfqMRYmj`ECt5MqJAQz?Rv$*Yz9Vs0JaBalXFkN{QAmm`4;V0T0U
zdeZw6$RvFvfh^Kl37jTfk^tq~U6(*MDHTivlzVqm!i`+&e3w8Tsb2#5z#e_I2&e>m
zR8bL}AvH>%kd!Jbg0rM05-1{VlE69AbqR>;Xk!ta2fpRtp!fnzfL9T^qj&RxK;R8u
z%PT1og+%B~so^|Fhr%ufkA`R0=vX*Z(ITz}9Jq32bV`y;u1aG$sqA>R!N|#3N@7AJ
z3sod5A~`*8lxa9_Ez55!uQLi(!|zo#RaRel&&s&9lFrGQr$Q-L+&wWn?lLh)LY>85
zZ!lOpZ?GwYb>xc8Rx{a2ao70#;~ry(rF|M+f9@BBdhxyM_SM5hKM>CQ=u_}DV{7iO
zdiE{(%5G|*RmBde3)RbUEpe-wlbZQ>O}s1q
rt+qkCaAGhaGk5{b-q+>fJZ1TCxu0Xj-ePM3lV7LHf79Dru^sUT{2lXI
delta 732
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 iCDTG?Q(kJ_U7
zr(Si0EF@S+_Dh!hlq9!Ap6mLh0VRn%M=pLN{9_Kaff7!d3{`zF!5~;JS*MN(SmSY7
z59+DV@-sQ+cw*sMLEr8w%=P&%`0H)Qk-LV@_BT@LP@QWy)j8p=|t6sSk20%IFM>lL%qdVVuI
zFi-^g-P!Zc&vKYoyER}TnYHahrmzi4-I3ln6E=gqCngl1KpwZ0m<-3RJHAxd4Ba^>
z0hm(PA^NR1Dyn~}a2y5R@ph*hYFE+V$qRZD;7rnxu~;S74?A{dcL_7xg9Wrh5lgiU
znNt-hv+YXIj%L1B@G!>+!@*O##;N?#He2dcVcminho-otc>GcWTP7-!fk+n&
ztilCBx3`Q)BkIx6_>G)Uao|vYkt
zs+QYx5B>GBHCWVyHnZX=hqPV2mm%6FRsuy);m1t^6bIGFys7$fAeg2|my=%ZQGHna
zavc4Ai=2^jSflQ?+8TpFuY-u=!}
z4F~!jYPc={P~r>nzs;QB;SC}g@MW9r}zxbv8HkS_6-!rH0^R9%6h*L$Wg3a-qA
zp6&g%=kvjeaUeWuBSZnf67*!}G_gT!YBVXT}U>OYWD}G%R_nk#CvLTwNYD;
zPKKdL{k}3VCdGdU>Y!BtcqQK@=xmY+Q<{s9pKA+SApFG|1#l}%M5#GV21P9n$OQ#;
z_MJhXN3x3s_{PFMYrrTsz5
z*l}4op4-gr(^a@Sc;|@OL*ox#{bG54wpZQq^1Ll@i`Cw8#t^u90q&Jy;IBGS-8@(w
z_HE)xVjMeVn;v@H$O*bgI)u6W9OE`0cqOa7^>y4RB61tE|
zSOAi9iY57S&T>?vT6pY>F5AyHrBh4dT+>&?qcQrltf;*rooQ`{?i1uiGN@7=tABOX#>G%&KawB(U``1wA6srLrr#MXyET#o12?!3C$b(ppl)=<0=w?c;?^
zpBUhy_eHXWK6b>)@c`$v;)wPx${@8|kX9jK;|9bbb*iZDRf{qO;}E{|r8N{Ixc%?n2^7XOzsytA*G*
zklT56241o4gAO-Yd
zPg-8bd#64?DEl3>pI^x_KflkvNmF2soe5I|{h>)=fZCA}9sXa;e?2(8y~%+LaIZNL)pOr;OgL6;l1Ho5o!<_5i5|8k(_@~BJ(3pqllx-q9UOR
zqH&}7qT{3Mp{HO#VYp(9V&-7cV@+aHWBXz^;9%l};d0|n;+f&i<5S`{5#STJ60{QP
z5q1&<5i|Vke+B{)0)7F4{hw?=fM|huft-Hq3fP~XssHY|OWc(7fFP=P&)nNeKtzD{
zwY1hCE5s%$OmKvqGs%2)k!40vQ95%v`Wtq4AmV|{3$3qQDKR<)0(mw!2HG!uN4&j5TQt4(A9DP1*XdLTG+mnyd#-4U-=#hIArQ<4!EIMvfuf11a&mB!++ghlDborGRQZ!CH-6;rV^
z8P1k+_TmSVDp;J8wkKk~ts9xF$@^KG{wdxtx%5+EJ3hZc`~zb4{Ah{Fvm;{YD!hr+
zf91TbaOqfl;Uwq9%hTMKPuKUep~G-8m?k!AFt9?2e}}mK=)?ELVf9bCsE2?M0Wy%B
zt|Tjy6C7iWC(H7p)8iBQL_g$PK47W#zOvLNm(O0%bLadUZ+8bgfDOgHgbmExBiK24Vq{TsB8^6Z}rb?4c=%C>}U;rcmEUW9yH<}Q0g9X
z;tm{=>K?q}9teuN4`qLgaD59*a0{J#3$A|)zjF&Z@`6!y(pcUlr+3i0wth)QgpSi=aG4LmfAvJBHhk98?3sZ
z=XyFE)JY{e5N%Axv8G?GP2Mr6&CHJA!jZZq%|aBds7fZ}k;m3f=E`_7{0|;wJ4+Fb
zX07_bKLybmKIX7^`8*}_QM5L3S(~aP8C{CYs@d>k&z;~1G |