From 718e8e1edb537aca984216be39b3be5c8af4e6da Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 16 May 2021 21:39:58 -0500 Subject: [PATCH 0001/1008] Create NsfwApiPolicy --- config/config.exs | 7 + .../web/activity_pub/mrf/nsfw_api_policy.ex | 185 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex diff --git a/config/config.exs b/config/config.exs index 66aee3264..d96dc1646 100644 --- a/config/config.exs +++ b/config/config.exs @@ -404,6 +404,13 @@ config :pleroma, :mrf_object_age, threshold: 604_800, actions: [:delist, :strip_followers] +config :pleroma, :mrf_nsfw_api, + url: "http://127.0.0.1:5000/", + threshold: 0.7, + mark_sensitive: true, + unlist: false, + reject: false + config :pleroma, :rich_media, enabled: true, ignore_hosts: [], diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex new file mode 100644 index 000000000..9ad175b1b --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do + @moduledoc """ + Hide, delete, or mark sensitive NSFW content with artificial intelligence. + + Requires a NSFW API server, configured like so: + + config :pleroma, Pleroma.Web.ActivityPub.MRF.NsfwMRF, + url: "http://127.0.0.1:5000/", + threshold: 0.8, + mark_sensitive: true, + unlist: false, + reject: false + + The NSFW API server must implement an HTTP endpoint like this: + + curl http://localhost:5000/?url=https://fedi.com/images/001.jpg + + Returning a response like this: + + {"score", 0.314} + + Where a score is 0-1, with `1` being definitely NSFW. + + A good API server is here: https://github.com/EugenCepoi/nsfw_api + You can run it with Docker with a one-liner: + + docker run -it -p 127.0.0.1:5000:5000/tcp --env PORT=5000 eugencepoi/nsfw_api:latest + + Options: + + - `url`: Base URL of the API server. Default: "http://127.0.0.1:5000/" + - `threshold`: Lowest score to take action on. Default: `0.7` + - `mark_sensitive`: Mark sensitive all detected NSFW content? Default: `true` + - `unlist`: Unlist all detected NSFW content? Default: `false` + - `reject`: Reject all detected NSFW content (takes precedence)? Default: `false` + """ + alias Pleroma.Config + alias Pleroma.Constants + alias Pleroma.HTTP + alias Pleroma.User + + require Logger + require Pleroma.Constants + + @behaviour Pleroma.Web.ActivityPub.MRF + @policy :mrf_nsfw_api + + defp build_request_url(url) do + Config.get([@policy, :url]) + |> URI.parse() + |> Map.put(:query, "url=#{url}") + |> URI.to_string() + end + + defp parse_url(url) do + request = build_request_url(url) + + with {:ok, %Tesla.Env{body: body}} <- HTTP.get(request) do + Jason.decode(body) + else + error -> + Logger.warn(""" + [NsfwApiPolicy]: The API server failed. Skipping. + #{inspect(error)} + """) + + error + end + end + + defp check_url_nsfw(url) when is_binary(url) do + threshold = Config.get([@policy, :threshold]) + + case parse_url(url) do + {:ok, %{"score" => score}} when score >= threshold -> + {:nsfw, %{url: url, score: score, threshold: threshold}} + + _ -> + {:sfw, url} + end + end + + defp check_url_nsfw(%{"href" => url}) when is_binary(url) do + check_url_nsfw(url) + end + + defp check_attachment_nsfw(%{"url" => urls} = attachment) when is_list(urls) do + if Enum.all?(urls, &match?({:sfw, _}, check_url_nsfw(&1))) do + {:sfw, attachment} + else + {:nsfw, attachment} + end + end + + defp check_object_nsfw(%{"attachment" => attachments} = object) when is_list(attachments) do + if Enum.all?(attachments, &match?({:sfw, _}, check_attachment_nsfw(&1))) do + {:sfw, object} + else + {:nsfw, object} + end + end + + defp check_object_nsfw(%{"object" => %{} = child_object} = object) do + case check_object_nsfw(child_object) do + {:sfw, _} -> {:sfw, object} + {:nsfw, _} -> {:nsfw, object} + end + end + + defp check_object_nsfw(object), do: {:sfw, object} + + @impl true + def filter(object) do + with {:sfw, object} <- check_object_nsfw(object) do + {:ok, object} + else + {:nsfw, _data} -> handle_nsfw(object) + _ -> {:reject, "NSFW: Attachment rejected"} + end + end + + defp handle_nsfw(object) do + if Config.get([@policy, :reject]) do + {:reject, object} + else + {:ok, + object + |> maybe_unlist() + |> maybe_mark_sensitive()} + end + end + + defp maybe_unlist(object) do + if Config.get([@policy, :unlist]) do + unlist(object) + else + object + end + end + + defp maybe_mark_sensitive(object) do + if Config.get([@policy, :mark_sensitive]) do + mark_sensitive(object) + else + object + end + end + + defp unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do + with %User{} = user <- User.get_cached_by_ap_id(actor) do + to = + [user.follower_address | to] + |> List.delete(Constants.as_public()) + |> Enum.uniq() + + cc = + [Constants.as_public() | cc] + |> List.delete(user.follower_address) + |> Enum.uniq() + + object + |> Map.put("to", to) + |> Map.put("cc", cc) + end + end + + defp mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do + Map.put(object, "object", mark_sensitive(child_object)) + end + + defp mark_sensitive(object) when is_map(object) do + tags = (object["tag"] || []) ++ ["nsfw"] + + object + |> Map.put("tag", tags) + |> Map.put("sensitive", true) + end + + @impl true + def describe, do: {:ok, %{}} +end From f15d419062b5f9aba2a2e84257dc2379b44f92e8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 16 Jun 2021 22:30:18 -0500 Subject: [PATCH 0002/1008] NsfwApiPolicy: raise if can't fetch user --- lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex index 9ad175b1b..63e6af0a0 100644 --- a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex @@ -165,6 +165,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do object |> Map.put("to", to) |> Map.put("cc", cc) + else + _ -> raise "[NsfwApiPolicy]: Could not fetch user #{actor}" end end From 2b3dfbb42f7ec0c5604876276a81d55a05955416 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 17 Jun 2021 14:36:51 -0500 Subject: [PATCH 0003/1008] NsfwApiPolicy: add tests --- .../web/activity_pub/mrf/nsfw_api_policy.ex | 45 ++- .../activity_pub/mrf/nsfw_api_policy_test.exs | 267 ++++++++++++++++++ 2 files changed, 299 insertions(+), 13 deletions(-) create mode 100644 test/pleroma/web/activity_pub/mrf/nsfw_api_policy_test.exs diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex index 63e6af0a0..9dcdf560e 100644 --- a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex @@ -49,14 +49,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do @behaviour Pleroma.Web.ActivityPub.MRF @policy :mrf_nsfw_api - defp build_request_url(url) do + def build_request_url(url) do Config.get([@policy, :url]) |> URI.parse() + |> fix_path() |> Map.put(:query, "url=#{url}") |> URI.to_string() end - defp parse_url(url) do + def parse_url(url) do request = build_request_url(url) with {:ok, %Tesla.Env{body: body}} <- HTTP.get(request) do @@ -72,23 +73,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do end end - defp check_url_nsfw(url) when is_binary(url) do + def check_url_nsfw(url) when is_binary(url) do threshold = Config.get([@policy, :threshold]) case parse_url(url) do {:ok, %{"score" => score}} when score >= threshold -> {:nsfw, %{url: url, score: score, threshold: threshold}} + {:ok, %{"score" => score}} -> + {:sfw, %{url: url, score: score, threshold: threshold}} + _ -> - {:sfw, url} + {:sfw, %{url: url, score: nil, threshold: threshold}} end end - defp check_url_nsfw(%{"href" => url}) when is_binary(url) do + def check_url_nsfw(%{"href" => url}) when is_binary(url) do check_url_nsfw(url) end - defp check_attachment_nsfw(%{"url" => urls} = attachment) when is_list(urls) do + def check_attachment_nsfw(%{"url" => urls} = attachment) when is_list(urls) do if Enum.all?(urls, &match?({:sfw, _}, check_url_nsfw(&1))) do {:sfw, attachment} else @@ -96,7 +100,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do end end - defp check_object_nsfw(%{"attachment" => attachments} = object) when is_list(attachments) do + def check_attachment_nsfw(%{"url" => url} = attachment) when is_binary(url) do + case check_url_nsfw(url) do + {:sfw, _} -> {:sfw, attachment} + {:nsfw, _} -> {:nsfw, attachment} + end + end + + def check_object_nsfw(%{"attachment" => attachments} = object) when is_list(attachments) do if Enum.all?(attachments, &match?({:sfw, _}, check_attachment_nsfw(&1))) do {:sfw, object} else @@ -104,14 +115,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do end end - defp check_object_nsfw(%{"object" => %{} = child_object} = object) do + def check_object_nsfw(%{"object" => %{} = child_object} = object) do case check_object_nsfw(child_object) do {:sfw, _} -> {:sfw, object} {:nsfw, _} -> {:nsfw, object} end end - defp check_object_nsfw(object), do: {:sfw, object} + def check_object_nsfw(object), do: {:sfw, object} @impl true def filter(object) do @@ -150,7 +161,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do end end - defp unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do + def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do with %User{} = user <- User.get_cached_by_ap_id(actor) do to = [user.follower_address | to] @@ -166,15 +177,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do |> Map.put("to", to) |> Map.put("cc", cc) else - _ -> raise "[NsfwApiPolicy]: Could not fetch user #{actor}" + _ -> raise "[NsfwApiPolicy]: Could not find user #{actor}" end end - defp mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do + def mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do Map.put(object, "object", mark_sensitive(child_object)) end - defp mark_sensitive(object) when is_map(object) do + def mark_sensitive(object) when is_map(object) do tags = (object["tag"] || []) ++ ["nsfw"] object @@ -182,6 +193,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do |> Map.put("sensitive", true) end + # Hackney needs a trailing slash + defp fix_path(%URI{path: path} = uri) when is_binary(path) do + path = String.trim_trailing(path, "/") <> "/" + Map.put(uri, :path, path) + end + + defp fix_path(%URI{path: nil} = uri), do: Map.put(uri, :path, "/") + @impl true def describe, do: {:ok, %{}} end diff --git a/test/pleroma/web/activity_pub/mrf/nsfw_api_policy_test.exs b/test/pleroma/web/activity_pub/mrf/nsfw_api_policy_test.exs new file mode 100644 index 000000000..0beb9c2cb --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/nsfw_api_policy_test.exs @@ -0,0 +1,267 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicyTest do + use Pleroma.DataCase + + import ExUnit.CaptureLog + import Pleroma.Factory + + alias Pleroma.Constants + alias Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy + + require Pleroma.Constants + + @policy :mrf_nsfw_api + + @sfw_url "https://kittens.co/kitty.gif" + @nsfw_url "https://b00bies.com/nsfw.jpg" + @timeout_url "http://time.out/i.jpg" + + setup_all do + clear_config(@policy, + url: "http://127.0.0.1:5000/", + threshold: 0.7, + mark_sensitive: true, + unlist: false, + reject: false + ) + end + + setup do + Tesla.Mock.mock(fn + # NSFW URL + %{method: :get, url: "http://127.0.0.1:5000/?url=#{@nsfw_url}"} -> + %Tesla.Env{status: 200, body: ~s({"score":0.99772077798843384,"url":"#{@nsfw_url}"})} + + # SFW URL + %{method: :get, url: "http://127.0.0.1:5000/?url=#{@sfw_url}"} -> + %Tesla.Env{status: 200, body: ~s({"score":0.00011714912398019806,"url":"#{@sfw_url}"})} + + # Timeout URL + %{method: :get, url: "http://127.0.0.1:5000/?url=#{@timeout_url}"} -> + {:error, :timeout} + + # Fallback URL + %{method: :get, url: "http://127.0.0.1:5000/?url=" <> url} -> + body = + ~s({"error_code":500,"error_reason":"[Errno -2] Name or service not known","url":"#{url}"}) + + %Tesla.Env{status: 500, body: body} + end) + + :ok + end + + describe "build_request_url/1" do + test "it works" do + expected = "http://127.0.0.1:5000/?url=https://b00bies.com/nsfw.jpg" + assert NsfwApiPolicy.build_request_url(@nsfw_url) == expected + end + + test "it adds a trailing slash" do + clear_config([@policy, :url], "http://localhost:5000") + + expected = "http://localhost:5000/?url=https://b00bies.com/nsfw.jpg" + assert NsfwApiPolicy.build_request_url(@nsfw_url) == expected + end + + test "it adds a trailing slash preserving the path" do + clear_config([@policy, :url], "http://localhost:5000/nsfw_api") + + expected = "http://localhost:5000/nsfw_api/?url=https://b00bies.com/nsfw.jpg" + assert NsfwApiPolicy.build_request_url(@nsfw_url) == expected + end + end + + describe "parse_url/1" do + test "returns decoded JSON from the API server" do + expected = %{"score" => 0.99772077798843384, "url" => @nsfw_url} + assert NsfwApiPolicy.parse_url(@nsfw_url) == {:ok, expected} + end + + test "warns when the API server fails" do + expected = "[NsfwApiPolicy]: The API server failed. Skipping." + assert capture_log(fn -> NsfwApiPolicy.parse_url(@timeout_url) end) =~ expected + end + + test "returns {:error, _} tuple when the API server fails" do + capture_log(fn -> + assert {:error, _} = NsfwApiPolicy.parse_url(@timeout_url) + end) + end + end + + describe "check_url_nsfw/1" do + test "returns {:nsfw, _} tuple" do + expected = {:nsfw, %{url: @nsfw_url, score: 0.99772077798843384, threshold: 0.7}} + assert NsfwApiPolicy.check_url_nsfw(@nsfw_url) == expected + end + + test "returns {:sfw, _} tuple" do + expected = {:sfw, %{url: @sfw_url, score: 0.00011714912398019806, threshold: 0.7}} + assert NsfwApiPolicy.check_url_nsfw(@sfw_url) == expected + end + + test "returns {:sfw, _} on failure" do + expected = {:sfw, %{url: @timeout_url, score: nil, threshold: 0.7}} + + capture_log(fn -> + assert NsfwApiPolicy.check_url_nsfw(@timeout_url) == expected + end) + end + + test "works with map URL" do + expected = {:nsfw, %{url: @nsfw_url, score: 0.99772077798843384, threshold: 0.7}} + assert NsfwApiPolicy.check_url_nsfw(%{"href" => @nsfw_url}) == expected + end + end + + describe "check_attachment_nsfw/1" do + test "returns {:nsfw, _} if any items are NSFW" do + attachment = %{"url" => [%{"href" => @nsfw_url}, @nsfw_url, @sfw_url]} + assert NsfwApiPolicy.check_attachment_nsfw(attachment) == {:nsfw, attachment} + end + + test "returns {:sfw, _} if all items are SFW" do + attachment = %{"url" => [%{"href" => @sfw_url}, @sfw_url, @sfw_url]} + assert NsfwApiPolicy.check_attachment_nsfw(attachment) == {:sfw, attachment} + end + + test "works with binary URL" do + attachment = %{"url" => @nsfw_url} + assert NsfwApiPolicy.check_attachment_nsfw(attachment) == {:nsfw, attachment} + end + end + + describe "check_object_nsfw/1" do + test "returns {:nsfw, _} if any items are NSFW" do + object = %{"attachment" => [%{"url" => [%{"href" => @nsfw_url}, @sfw_url]}]} + assert NsfwApiPolicy.check_object_nsfw(object) == {:nsfw, object} + end + + test "returns {:sfw, _} if all items are SFW" do + object = %{"attachment" => [%{"url" => [%{"href" => @sfw_url}, @sfw_url]}]} + assert NsfwApiPolicy.check_object_nsfw(object) == {:sfw, object} + end + + test "works with embedded object" do + object = %{"object" => %{"attachment" => [%{"url" => [%{"href" => @nsfw_url}, @sfw_url]}]}} + assert NsfwApiPolicy.check_object_nsfw(object) == {:nsfw, object} + end + end + + describe "unlist/1" do + test "unlist addressing" do + user = insert(:user) + + object = %{ + "to" => [Constants.as_public()], + "cc" => [user.follower_address, "https://hello.world/users/alex"], + "actor" => user.ap_id + } + + expected = %{ + "to" => [user.follower_address], + "cc" => [Constants.as_public(), "https://hello.world/users/alex"], + "actor" => user.ap_id + } + + assert NsfwApiPolicy.unlist(object) == expected + end + + test "raise if user isn't found" do + object = %{ + "to" => [Constants.as_public()], + "cc" => [], + "actor" => "https://hello.world/users/alex" + } + + assert_raise(RuntimeError, fn -> + NsfwApiPolicy.unlist(object) + end) + end + end + + describe "mark_sensitive/1" do + test "adds nsfw tag and marks sensitive" do + object = %{"tag" => ["yolo"]} + expected = %{"tag" => ["yolo", "nsfw"], "sensitive" => true} + assert NsfwApiPolicy.mark_sensitive(object) == expected + end + + test "works with embedded object" do + object = %{"object" => %{"tag" => ["yolo"]}} + expected = %{"object" => %{"tag" => ["yolo", "nsfw"], "sensitive" => true}} + assert NsfwApiPolicy.mark_sensitive(object) == expected + end + end + + describe "filter/1" do + setup do + user = insert(:user) + + nsfw_object = %{ + "to" => [Constants.as_public()], + "cc" => [user.follower_address], + "actor" => user.ap_id, + "attachment" => [%{"url" => @nsfw_url}] + } + + sfw_object = %{ + "to" => [Constants.as_public()], + "cc" => [user.follower_address], + "actor" => user.ap_id, + "attachment" => [%{"url" => @sfw_url}] + } + + %{user: user, nsfw_object: nsfw_object, sfw_object: sfw_object} + end + + test "passes SFW object through", %{sfw_object: object} do + {:ok, _} = NsfwApiPolicy.filter(object) + end + + test "passes NSFW object through when actions are disabled", %{nsfw_object: object} do + clear_config([@policy, :mark_sensitive], false) + clear_config([@policy, :unlist], false) + clear_config([@policy, :reject], false) + {:ok, _} = NsfwApiPolicy.filter(object) + end + + test "passes NSFW object through when :threshold is 1", %{nsfw_object: object} do + clear_config([@policy, :reject], true) + clear_config([@policy, :threshold], 1) + {:ok, _} = NsfwApiPolicy.filter(object) + end + + test "rejects SFW object through when :threshold is 0", %{sfw_object: object} do + clear_config([@policy, :reject], true) + clear_config([@policy, :threshold], 0) + {:reject, _} = NsfwApiPolicy.filter(object) + end + + test "rejects NSFW when :reject is enabled", %{nsfw_object: object} do + clear_config([@policy, :reject], true) + {:reject, _} = NsfwApiPolicy.filter(object) + end + + test "passes NSFW through when :reject is disabled", %{nsfw_object: object} do + clear_config([@policy, :reject], false) + {:ok, _} = NsfwApiPolicy.filter(object) + end + + test "unlists NSFW when :unlist is enabled", %{user: user, nsfw_object: object} do + clear_config([@policy, :unlist], true) + {:ok, object} = NsfwApiPolicy.filter(object) + assert object["to"] == [user.follower_address] + end + + test "passes NSFW through when :unlist is disabled", %{nsfw_object: object} do + clear_config([@policy, :unlist], false) + {:ok, object} = NsfwApiPolicy.filter(object) + assert object["to"] == [Constants.as_public()] + end + end +end From b293c14a1b01398029dfa80aea306946efc2f284 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 17 Jun 2021 14:52:07 -0500 Subject: [PATCH 0004/1008] NsfwApiPolicy: add describe/0 and config_description/0 --- .../web/activity_pub/mrf/nsfw_api_policy.ex | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex index 9dcdf560e..a1560c584 100644 --- a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do config :pleroma, Pleroma.Web.ActivityPub.MRF.NsfwMRF, url: "http://127.0.0.1:5000/", - threshold: 0.8, + threshold: 0.7, mark_sensitive: true, unlist: false, reject: false @@ -202,5 +202,57 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do defp fix_path(%URI{path: nil} = uri), do: Map.put(uri, :path, "/") @impl true - def describe, do: {:ok, %{}} + def describe do + options = %{ + threshold: Config.get([@policy, :threshold]), + mark_sensitive: Config.get([@policy, :mark_sensitive]), + unlist: Config.get([@policy, :unlist]), + reject: Config.get([@policy, :reject]) + } + + {:ok, %{@policy => options}} + end + + @impl true + def config_description do + %{ + key: @policy, + related_policy: to_string(__MODULE__), + label: "NSFW API Policy", + description: + "Hide, delete, or mark sensitive NSFW content with artificial intelligence. Requires running an external API server.", + children: [ + %{ + key: :url, + type: :string, + description: "Base URL of the API server.", + suggestions: ["http://127.0.0.1:5000/"] + }, + %{ + key: :threshold, + type: :float, + description: "Lowest score to take action on. Between 0 and 1.", + suggestions: [0.7] + }, + %{ + key: :mark_sensitive, + type: :boolean, + description: "Mark sensitive all detected NSFW content?", + suggestions: [true] + }, + %{ + key: :unlist, + type: :boolean, + description: "Unlist sensitive all detected NSFW content?", + suggestions: [false] + }, + %{ + key: :reject, + type: :boolean, + description: "Reject sensitive all detected NSFW content (takes precedence)?", + suggestions: [false] + } + ] + } + end end From c802c3055ef6c1f763d5df68f9e5308093f7d565 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 17 Jun 2021 15:04:40 -0500 Subject: [PATCH 0005/1008] NsfwApiPolicy: add systemd example file --- installation/nsfw-api.service | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 installation/nsfw-api.service diff --git a/installation/nsfw-api.service b/installation/nsfw-api.service new file mode 100644 index 000000000..ec629df67 --- /dev/null +++ b/installation/nsfw-api.service @@ -0,0 +1,15 @@ +[Unit] +Description=NSFW API +After=docker.service +Requires=docker.service + +[Service] +TimeoutStartSec=0 +Restart=always +ExecStartPre=-/usr/bin/docker stop %n +ExecStartPre=-/usr/bin/docker rm %n +ExecStartPre=/usr/bin/docker pull eugencepoi/nsfw_api:latest +ExecStart=/usr/bin/docker run --rm -p 127.0.0.1:5000:5000/tcp --env PORT=5000 --name %n eugencepoi/nsfw_api:latest + +[Install] +WantedBy=multi-user.target From a704d5499c03cb5609ea38a5f2ef06095ced3ef3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 17 Jun 2021 15:32:42 -0500 Subject: [PATCH 0006/1008] NsfwApiPolicy: Fall back more generously when functions don't match --- lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex index a1560c584..920821f38 100644 --- a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex @@ -92,6 +92,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do check_url_nsfw(url) end + def check_url_nsfw(url) do + threshold = Config.get([@policy, :threshold]) + {:sfw, %{url: url, score: nil, threshold: threshold}} + end + def check_attachment_nsfw(%{"url" => urls} = attachment) when is_list(urls) do if Enum.all?(urls, &match?({:sfw, _}, check_url_nsfw(&1))) do {:sfw, attachment} @@ -107,6 +112,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do end end + def check_attachment_nsfw(attachment), do: {:sfw, attachment} + def check_object_nsfw(%{"attachment" => attachments} = object) when is_list(attachments) do if Enum.all?(attachments, &match?({:sfw, _}, check_attachment_nsfw(&1))) do {:sfw, object} From 9423052e9217aa1358950d37c5c96b11d554b37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 25 Apr 2022 12:39:36 +0200 Subject: [PATCH 0007/1008] Add "status" notification type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/notification.ex | 38 ++++++++++++-- .../operations/notification_operation.ex | 4 +- .../controllers/notification_controller.ex | 1 + .../mastodon_api/views/notification_view.ex | 3 ++ lib/pleroma/web/push/impl.ex | 1 + ...00000_add_status_to_notifications_enum.exs | 50 +++++++++++++++++++ test/pleroma/notification_test.exs | 1 + 7 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 52fd2656b..d142baa8b 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -73,6 +73,7 @@ defmodule Pleroma.Notification do pleroma:report reblog poll + status } def changeset(%Notification{} = notification, attrs) do @@ -397,11 +398,18 @@ defmodule Pleroma.Notification do {enabled_receivers, disabled_receivers} = get_notified_from_activity(activity) potential_receivers = enabled_receivers ++ disabled_receivers + {enabled_subscribers, disabled_subscribers} = get_notified_subscribers_from_activity(activity) + potential_subscribers = (enabled_subscribers ++ disabled_subscribers) -- potential_receivers + notifications = - Enum.map(potential_receivers, fn user -> - do_send = do_send && user in enabled_receivers - create_notification(activity, user, do_send: do_send) - end) + (Enum.map(potential_receivers, fn user -> + do_send = do_send && user in enabled_receivers + create_notification(activity, user, do_send: do_send) + end) ++ + Enum.map(potential_subscribers, fn user -> + do_send = do_send && user in enabled_subscribers + create_notification(activity, user, do_send: do_send, type: "status") + end)) |> Enum.reject(&is_nil/1) {:ok, notifications} @@ -533,6 +541,27 @@ defmodule Pleroma.Notification do def get_notified_from_activity(_, _local_only), do: {[], []} + def get_notified_subscribers_from_activity(activity, local_only \\ true) + + def get_notified_subscribers_from_activity( + %Activity{data: %{"type" => "Create"}} = activity, + local_only + ) do + notification_enabled_ap_ids = + [] + |> Utils.maybe_notify_subscribers(activity) + + potential_receivers = + User.get_users_from_set(notification_enabled_ap_ids, local_only: local_only) + + notification_enabled_users = + Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end) + + {notification_enabled_users, potential_receivers -- notification_enabled_users} + end + + def get_notified_subscribers_from_activity(_, _), do: {[], []} + # For some activities, only notify the author of the object def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_id}}) when type in ~w{Like Announce EmojiReact} do @@ -557,7 +586,6 @@ defmodule Pleroma.Notification do [] |> Utils.maybe_notify_to_recipients(activity) |> Utils.maybe_notify_mentioned_recipients(activity) - |> Utils.maybe_notify_subscribers(activity) |> Utils.maybe_notify_followers(activity) |> Enum.uniq() end diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex index 7f2336ff6..aa965fabb 100644 --- a/lib/pleroma/web/api_spec/operations/notification_operation.ex +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -196,7 +196,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do "pleroma:report", "move", "follow_request", - "poll" + "poll", + "status" ], description: """ The type of event that resulted in the notification. @@ -210,6 +211,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do - `pleroma:emoji_reaction` - Someone reacted with emoji to your status - `pleroma:chat_mention` - Someone mentioned you in a chat message - `pleroma:report` - Someone was reported + - `status` - Someone you are subscribed to created a status """ } end diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 932bc6423..9209e8ebd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -51,6 +51,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do move pleroma:emoji_reaction poll + status } def index(%{assigns: %{user: user}} = conn, params) do params = diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 0dc7f3beb..b10b0893c 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -103,6 +103,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do "mention" -> put_status(response, activity, reading_user, status_render_opts) + "status" -> + put_status(response, activity, reading_user, status_render_opts) + "favourite" -> put_status(response, parent_activity_fn.(), reading_user, status_render_opts) diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index daf3eeb9e..77bc2941d 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -183,6 +183,7 @@ defmodule Pleroma.Web.Push.Impl do def format_title(%{type: type}, mastodon_type) do case mastodon_type || type do "mention" -> "New Mention" + "status" -> "New Status" "follow" -> "New Follower" "follow_request" -> "New Follow Request" "reblog" -> "New Repeat" diff --git a/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs b/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs new file mode 100644 index 000000000..62c0afb63 --- /dev/null +++ b/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs @@ -0,0 +1,50 @@ +defmodule Pleroma.Repo.Migrations.AddStatusToNotificationsEnum do + use Ecto.Migration + + @disable_ddl_transaction true + + def up do + """ + alter type notification_type add value 'status' + """ + |> execute() + end + + def down do + alter table(:notifications) do + modify(:type, :string) + end + + """ + delete from notifications where type = 'status' + """ + |> execute() + + """ + drop type if exists notification_type + """ + |> execute() + + """ + create type notification_type as enum ( + 'follow', + 'follow_request', + 'mention', + 'move', + 'pleroma:emoji_reaction', + 'pleroma:chat_mention', + 'reblog', + 'favourite', + 'pleroma:report', + 'poll + ) + """ + |> execute() + + """ + alter table notifications + alter column type type notification_type using (type::notification_type) + """ + |> execute() + end +end diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index 805764ea4..eea2fcb67 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -104,6 +104,7 @@ defmodule Pleroma.NotificationTest do {:ok, [notification]} = Notification.create_notifications(status) assert notification.user_id == subscriber.id + assert notification.type == "status" end test "does not create a notification for subscribed users if status is a reply" do From bd52e2aec7f07da3bc3609f72f7f1bf5969c9baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Feb 2022 18:03:53 +0100 Subject: [PATCH 0008/1008] Instance rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 56 +++++++++ .../admin_api/controllers/rule_controller.ex | 64 ++++++++++ lib/pleroma/web/admin_api/views/rule_view.ex | 21 ++++ .../operations/admin/rule_operation.ex | 113 ++++++++++++++++++ .../web/mastodon_api/views/instance_view.ex | 7 ++ lib/pleroma/web/router.ex | 5 + .../20220203224011_create_rules.exs | 12 ++ .../controllers/rule_controller_test.exs | 78 ++++++++++++ .../controllers/instance_controller_test.exs | 18 ++- 9 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 lib/pleroma/rule.ex create mode 100644 lib/pleroma/web/admin_api/controllers/rule_controller.ex create mode 100644 lib/pleroma/web/admin_api/views/rule_view.ex create mode 100644 lib/pleroma/web/api_spec/operations/admin/rule_operation.ex create mode 100644 priv/repo/migrations/20220203224011_create_rules.exs create mode 100644 test/pleroma/web/admin_api/controllers/rule_controller_test.exs diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex new file mode 100644 index 000000000..d772a32bd --- /dev/null +++ b/lib/pleroma/rule.ex @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Rule do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Repo + alias Pleroma.Rule + + schema "rules" do + field(:priority, :integer, default: 0) + field(:text, :string) + + timestamps() + end + + def changeset(%Rule{} = rule, params \\ %{}) do + rule + |> cast(params, [:priority, :text]) + |> validate_required([:text]) + end + + def query do + Rule + |> order_by(asc: :priority) + end + + def get(id), do: Repo.get(__MODULE__, id) + + def create(params) do + {:ok, rule} = + %Rule{} + |> changeset(params) + |> Repo.insert() + + rule + end + + def update(params, id) do + {:ok, rule} = + get(id) + |> changeset(params) + |> Repo.update() + + rule + end + + def delete(id) do + get(id) + |> Repo.delete() + end +end diff --git a/lib/pleroma/web/admin_api/controllers/rule_controller.ex b/lib/pleroma/web/admin_api/controllers/rule_controller.ex new file mode 100644 index 000000000..2db88b6ba --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/rule_controller.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleController do + use Pleroma.Web, :controller + + alias Pleroma.Repo + alias Pleroma.Rule + alias Pleroma.Web.Plugs.OAuthScopesPlug + + import Pleroma.Web.ControllerHelper, + only: [ + json_response: 3 + ] + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["admin:write"]} + when action in [:create, :update, :delete] + ) + + plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :index) + + action_fallback(AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.RuleOperation + + def index(conn, _) do + rules = + Rule.query() + |> Repo.all() + + render(conn, "index.json", rules: rules) + end + + def create(%{body_params: params} = conn, _) do + rule = + params + |> Rule.create() + + render(conn, "show.json", rule: rule) + end + + def update(%{body_params: params} = conn, %{id: id}) do + rule = + params + |> Rule.update(id) + + render(conn, "show.json", rule: rule) + end + + def delete(conn, %{id: id}) do + with {:ok, _} <- Rule.delete(id) do + json(conn, %{}) + else + _ -> json_response(conn, :bad_request, "") + end + end +end diff --git a/lib/pleroma/web/admin_api/views/rule_view.ex b/lib/pleroma/web/admin_api/views/rule_view.ex new file mode 100644 index 000000000..f29145248 --- /dev/null +++ b/lib/pleroma/web/admin_api/views/rule_view.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleView do + use Pleroma.Web, :view + + require Pleroma.Constants + + def render("index.json", %{rules: rules} = _opts) do + render_many(rules, __MODULE__, "show.json") + end + + def render("show.json", %{rule: rule} = _opts) do + %{ + id: rule.id, + priority: rule.priority, + text: rule.text + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex new file mode 100644 index 000000000..28f2be5e7 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -0,0 +1,113 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Retrieve a list of instance rules", + operationId: "AdminAPI.RuleController.index", + security: [%{"oAuth" => ["admin:read"]}], + responses: %{ + 200 => + Operation.response("Response", "application/json", %Schema{ + type: :array, + items: rule() + }), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + def create_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Create new rule", + operationId: "AdminAPI.RuleController.create", + security: [%{"oAuth" => ["admin:write"]}], + parameters: admin_api_params(), + requestBody: request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Response", "application/json", rule()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Modify existing rule", + operationId: "AdminAPI.RuleController.update", + security: [%{"oAuth" => ["admin:write"]}], + parameters: [Operation.parameter(:id, :path, :string, "Rule ID")], + requestBody: request_body("Parameters", update_request(), required: true), + responses: %{ + 200 => Operation.response("Response", "application/json", rule()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Delete rule", + operationId: "AdminAPI.RuleController.delete", + parameters: [Operation.parameter(:id, :path, :string, "Rule ID")], + security: [%{"oAuth" => ["admin:write"]}], + responses: %{ + 200 => empty_object_response(), + 404 => Operation.response("Not Found", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + type: :object, + required: [:text], + properties: %{ + priority: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } + end + + defp update_request do + %Schema{ + type: :object, + properties: %{ + priority: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } + end + + defp rule do + %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + priority: %Schema{type: :integer}, + text: %Schema{type: :string}, + created_at: %Schema{type: :string, format: :"date-time"} + } + } + end +end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index ee52475d5..9652da53a 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -40,6 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), + rules: rules(), pleroma: %{ metadata: %{ account_activation_required: Keyword.get(instance, :account_activation_required), @@ -137,4 +138,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end + + def rules do + Pleroma.Rule.query() + |> Pleroma.Repo.all() + |> Enum.map(&%{id: &1.id, text: &1.text}) + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ceb6c3cfd..e8b7b17aa 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -229,6 +229,11 @@ defmodule Pleroma.Web.Router do post("/frontends/install", FrontendController, :install) post("/backups", AdminAPIController, :create_backup) + + get("/rules", RuleController, :index) + post("/rules", RuleController, :create) + patch("/rules/:id", RuleController, :update) + delete("/rules/:id", RuleController, :delete) end # AdminAPI: admins and mods (staff) can perform these actions (if enabled by config) diff --git a/priv/repo/migrations/20220203224011_create_rules.exs b/priv/repo/migrations/20220203224011_create_rules.exs new file mode 100644 index 000000000..16f29ca53 --- /dev/null +++ b/priv/repo/migrations/20220203224011_create_rules.exs @@ -0,0 +1,12 @@ +defmodule Pleroma.Repo.Migrations.CreateRules do + use Ecto.Migration + + def change do + create_if_not_exists table(:rules) do + add(:priority, :integer, default: 0, null: false) + add(:text, :text, null: false) + + timestamps() + end + end +end diff --git a/test/pleroma/web/admin_api/controllers/rule_controller_test.exs b/test/pleroma/web/admin_api/controllers/rule_controller_test.exs new file mode 100644 index 000000000..c5c72d293 --- /dev/null +++ b/test/pleroma/web/admin_api/controllers/rule_controller_test.exs @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleControllerTest do + use Pleroma.Web.ConnCase, async: true + + import Pleroma.Factory + + alias Pleroma.Rule + + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "GET /api/pleroma/admin/rules" do + test "sorts rules by priority", %{conn: conn} do + %{id: id1} = Rule.create(%{text: "Example rule"}) + %{id: id2} = Rule.create(%{text: "Second rule", priority: 2}) + %{id: id3} = Rule.create(%{text: "Third rule", priority: 1}) + + response = + conn + |> get("/api/pleroma/admin/rules") + |> json_response_and_validate_schema(:ok) + + assert [%{"id" => ^id1}, %{"id" => ^id3}, %{"id" => ^id2}] = response + end + end + + describe "POST /api/pleroma/admin/rules" do + test "creates a rule", %{conn: conn} do + %{"id" => id} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/pleroma/admin/rules", %{text: "Example rule"}) + |> json_response_and_validate_schema(:ok) + + assert %{text: "Example rule"} = Rule.get(id) + end + end + + describe "PATCH /api/pleroma/admin/rules" do + test "edits a rule", %{conn: conn} do + %{id: id} = Rule.create(%{text: "Example rule"}) + + conn + |> put_req_header("content-type", "application/json") + |> patch("/api/pleroma/admin/rules/#{id}", %{text: "There are no rules", priority: 2}) + |> json_response_and_validate_schema(:ok) + + assert %{text: "There are no rules", priority: 2} = Rule.get(id) + end + end + + describe "DELETE /api/pleroma/admin/rules" do + test "deletes a rule", %{conn: conn} do + %{id: id} = Rule.create(%{text: "Example rule"}) + + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/pleroma/admin/rules/#{id}") + |> json_response_and_validate_schema(:ok) + + assert [] = + Rule.query() + |> Pleroma.Repo.all() + end + end +end diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index 9845408d6..87beacd64 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do # TODO: Should not need Cachex use Pleroma.Web.ConnCase + alias Pleroma.Rule alias Pleroma.User import Pleroma.Factory @@ -39,7 +40,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do "banner_upload_limit" => _, "background_image" => from_config_background, "shout_limit" => _, - "description_limit" => _ + "description_limit" => _, + "rules" => _ } = result assert result["pleroma"]["metadata"]["account_activation_required"] != nil @@ -91,4 +93,18 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do assert ["peer1.com", "peer2.com"] == Enum.sort(result) end + + test "get instance rules", %{conn: conn} do + Rule.create(%{text: "Example rule"}) + Rule.create(%{text: "Second rule"}) + Rule.create(%{text: "Third rule"}) + + conn = get(conn, "/api/v1/instance") + + assert result = json_response_and_validate_schema(conn, 200) + + rules = result["rules"] + + assert length(rules) == 3 + end end From 432599311d3aab8829ed2cc7f795a1662e7a8f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Feb 2022 19:13:30 +0100 Subject: [PATCH 0009/1008] Add GET /api/v1/instance/rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../operations/admin/rule_operation.ex | 2 +- .../api_spec/operations/instance_operation.ex | 27 ++++++++++++++++++- .../controllers/instance_controller.ex | 5 ++++ .../web/mastodon_api/views/instance_view.ex | 4 +++ lib/pleroma/web/router.ex | 1 + 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex index 28f2be5e7..ed0d9eaf6 100644 --- a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do def index_operation do %Operation{ tags: ["Instance rule managment"], - summary: "Retrieve a list of instance rules", + summary: "Retrieve list of instance rules", operationId: "AdminAPI.RuleController.index", security: [%{"oAuth" => ["admin:read"]}], responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 3c4b504fe..e66e5b7a3 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -34,6 +34,17 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def rules_operation do + %Operation{ + tags: ["Instance"], + summary: "Retrieve list of instance rules", + operationId: "InstanceController.rules", + responses: %{ + 200 => Operation.response("Array of domains", "application/json", array_of_rules()) + } + } + end + defp instance do %Schema{ type: :object, @@ -160,7 +171,8 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do "urls" => %{ "streaming_api" => "wss://lain.com" }, - "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)" + "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)", + "rules" => array_of_rules() } } end @@ -172,4 +184,17 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do example: ["pleroma.site", "lain.com", "bikeshed.party"] } end + + defp array_of_rules do + %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } + } + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 6410e872c..d6aa89432 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -20,4 +20,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do def peers(conn, _params) do json(conn, Pleroma.Stats.get_peers()) end + + @doc "GET /api/v1/instance/rules" + def rules(conn, _params) do + render(conn, "rules.json") + end end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 9652da53a..feb94b40b 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -58,6 +58,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + def render("rules.json", _) do + rules() + end + def features do [ "pleroma_api", diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e8b7b17aa..1246989b0 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -606,6 +606,7 @@ defmodule Pleroma.Web.Router do get("/instance", InstanceController, :show) get("/instance/peers", InstanceController, :peers) + get("/instance/rules", InstanceController, :rules) get("/statuses", StatusController, :index) get("/statuses/:id", StatusController, :show) From 384f8bfa786f51f3abec101e2ab78917f324a4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 7 Feb 2022 23:41:41 +0100 Subject: [PATCH 0010/1008] Instance rules: Use render_many MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../admin_api/controllers/rule_controller.ex | 2 -- .../web/mastodon_api/views/instance_view.ex | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/web/admin_api/controllers/rule_controller.ex b/lib/pleroma/web/admin_api/controllers/rule_controller.ex index 2db88b6ba..43b2f209a 100644 --- a/lib/pleroma/web/admin_api/controllers/rule_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/rule_controller.ex @@ -14,8 +14,6 @@ defmodule Pleroma.Web.AdminAPI.RuleController do json_response: 3 ] - require Logger - plug(Pleroma.Web.ApiSpec.CastAndValidate) plug( diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index feb94b40b..8379731e4 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -40,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), - rules: rules(), + rules: render(__MODULE__, "rules.json"), pleroma: %{ metadata: %{ account_activation_required: Keyword.get(instance, :account_activation_required), @@ -59,7 +59,16 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do end def render("rules.json", _) do - rules() + Pleroma.Rule.query() + |> Pleroma.Repo.all() + |> render_many(__MODULE__, "rule.json", as: :rule) + end + + def render("rule.json", %{rule: rule}) do + %{ + id: rule.id, + text: rule.text + } end def features do @@ -142,10 +151,4 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end - - def rules do - Pleroma.Rule.query() - |> Pleroma.Repo.all() - |> Enum.map(&%{id: &1.id, text: &1.text}) - end end From bbf3bc2228ca4bc5c209e418538665240e2aa9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 7 Feb 2022 23:55:20 +0100 Subject: [PATCH 0011/1008] Add RuleTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- test/pleroma/rule_test.exs | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/pleroma/rule_test.exs diff --git a/test/pleroma/rule_test.exs b/test/pleroma/rule_test.exs new file mode 100644 index 000000000..012ac902c --- /dev/null +++ b/test/pleroma/rule_test.exs @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.RuleTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Repo + alias Pleroma.Rule + + test "getting a list of rules sorted by priority" do + %{id: id1} = Rule.create(%{text: "Example rule"}) + %{id: id2} = Rule.create(%{text: "Second rule", priority: 2}) + %{id: id3} = Rule.create(%{text: "Third rule", priority: 1}) + + rules = + Rule.query() + |> Repo.all() + + assert [%{id: ^id1}, %{id: ^id3}, %{id: ^id2}] = rules + end + + test "creating rules" do + %{id: id} = Rule.create(%{text: "Example rule"}) + + assert %{text: "Example rule"} = Rule.get(id) + end + + test "editing rules" do + %{id: id} = Rule.create(%{text: "Example rule"}) + + Rule.update(%{text: "There are no rules", priority: 2}, id) + + assert %{text: "There are no rules", priority: 2} = Rule.get(id) + end + + test "deleting rules" do + %{id: id} = Rule.create(%{text: "Example rule"}) + + Rule.delete(id) + + assert [] = + Rule.query() + |> Pleroma.Repo.all() + end +end From 574db5b988e3caca14d1729a729af83d2f23e214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 20 Feb 2022 21:44:42 +0100 Subject: [PATCH 0012/1008] Allow submitting an array of rule_ids to /api/v1/reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/constants.ex | 3 ++- lib/pleroma/rule.ex | 5 ++++ lib/pleroma/web/activity_pub/utils.ex | 8 +++++-- .../web/admin_api/views/report_view.ex | 15 +++++++++++- .../operations/admin/report_operation.ex | 10 ++++++++ .../api_spec/operations/report_operation.ex | 9 +++++++- lib/pleroma/web/common_api.ex | 17 ++++++++++++-- .../web/admin_api/views/report_view_test.exs | 23 +++++++++++++++++-- .../controllers/report_controller_test.exs | 21 +++++++++++++++++ 9 files changed, 102 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index a42c71d23..bf43becb3 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Constants do "context_id", "deleted_activity_id", "pleroma_internal", - "generator" + "generator", + "rules" ] ) diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index d772a32bd..486cff8cc 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -29,6 +29,11 @@ defmodule Pleroma.Rule do |> order_by(asc: :priority) end + def get(ids) when is_list(ids) do + from(r in __MODULE__, where: r.id in ^ids) + |> Repo.all() + end + def get(id), do: Repo.get(__MODULE__, id) def create(params) do diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 9cde7805c..72d17e2aa 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -692,14 +692,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Flag-related helpers @spec make_flag_data(map(), map()) :: map() - def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do + def make_flag_data( + %{actor: actor, context: context, content: content} = params, + additional + ) do %{ "type" => "Flag", "actor" => actor.ap_id, "content" => content, "object" => build_flag_object(params), "context" => context, - "state" => "open" + "state" => "open", + "rules" => Map.get(params, :rules, nil) } |> Map.merge(additional) end diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index b761dbb22..ca70f4359 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -6,10 +6,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do use Pleroma.Web, :view alias Pleroma.HTML + alias Pleroma.Rule alias Pleroma.User alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.MastodonAPI.StatusView defdelegate merge_account_views(user), to: AdminAPI.AccountView @@ -46,7 +48,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do as: :activity }), state: report.data["state"], - notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) + notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}), + rules: rules(Map.get(report.data, "rules", nil)) } end @@ -71,4 +74,14 @@ defmodule Pleroma.Web.AdminAPI.ReportView do created_at: Utils.to_masto_date(inserted_at) } end + + defp rules(nil) do + [] + end + + defp rules(rule_ids) do + rule_ids + |> Rule.get() + |> render_many(InstanceView, "rule.json", as: :rule) + end end diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index 312e091a5..bb71abbd1 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -169,6 +169,16 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do inserted_at: %Schema{type: :string, format: :"date-time"} } } + }, + rules: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } } } } diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex index c74ac7d5f..fd68f67a2 100644 --- a/lib/pleroma/web/api_spec/operations/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -53,6 +53,12 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do default: false, description: "If the account is remote, should the report be forwarded to the remote admin?" + }, + rule_ids: %Schema{ + type: :array, + nullable: true, + items: %Schema{type: :number}, + description: "Array of rules" } }, required: [:account_id], @@ -60,7 +66,8 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do "account_id" => "123", "status_ids" => ["1337"], "comment" => "bad status!", - "forward" => "false" + "forward" => "false", + "rule_ids" => [3] } } end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 1b95ee89c..9f8d4def4 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object + alias Pleroma.Rule alias Pleroma.ThreadMute alias Pleroma.User alias Pleroma.UserRelationship @@ -505,14 +506,16 @@ defmodule Pleroma.Web.CommonAPI do def report(user, data) do with {:ok, account} <- get_reported_account(data.account_id), {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]), - {:ok, statuses} <- get_report_statuses(account, data) do + {:ok, statuses} <- get_report_statuses(account, data), + rules <- get_report_rules(Map.get(data, :rule_ids, nil)) do ActivityPub.flag(%{ context: Utils.generate_context_id(), actor: user, account: account, statuses: statuses, content: content_html, - forward: Map.get(data, :forward, false) + forward: Map.get(data, :forward, false), + rules: rules }) end end @@ -524,6 +527,16 @@ defmodule Pleroma.Web.CommonAPI do end end + defp get_report_rules(nil) do + nil + end + + defp get_report_rules(rule_ids) do + rule_ids + |> Rule.get() + |> Enum.map(& &1.id) + end + def update_report_state(activity_ids, state) when is_list(activity_ids) do case Utils.update_report_state(activity_ids, state) do :ok -> {:ok, activity_ids} diff --git a/test/pleroma/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs index 9637c2b90..519208b45 100644 --- a/test/pleroma/web/admin_api/views/report_view_test.exs +++ b/test/pleroma/web/admin_api/views/report_view_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do import Pleroma.Factory + alias Pleroma.Rule alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.AdminAPI.ReportView @@ -38,7 +39,8 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do statuses: [], notes: [], state: "open", - id: activity.id + id: activity.id, + rules: [] } result = @@ -76,7 +78,8 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do statuses: [StatusView.render("show.json", %{activity: activity})], state: "open", notes: [], - id: report_activity.id + id: report_activity.id, + rules: [] } result = @@ -168,4 +171,20 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do assert report2.id == rendered |> Enum.at(0) |> Map.get(:id) assert report1.id == rendered |> Enum.at(1) |> Map.get(:id) end + + test "renders included rules" do + user = insert(:user) + other_user = insert(:user) + + %{id: id, text: text} = Rule.create(%{text: "Example rule"}) + + {:ok, activity} = + CommonAPI.report(user, %{ + account_id: other_user.id, + rule_ids: [id] + }) + + assert %{rules: [%{id: ^id, text: ^text}]} = + ReportView.render("show.json", Report.extract_report_info(activity)) + end end diff --git a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs index 6d1a63334..77626b821 100644 --- a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs @@ -5,6 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do use Pleroma.Web.ConnCase, async: true + alias Pleroma.Activity + alias Pleroma.Rule alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -44,6 +46,25 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do |> json_response_and_validate_schema(200) end + test "submit a report with rule_ids", %{ + conn: conn, + target_user: target_user + } do + %{id: rule_id} = Rule.create(%{text: "There are no rules"}) + + assert %{"action_taken" => false, "id" => id} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/reports", %{ + "account_id" => target_user.id, + "forward" => "false", + "rule_ids" => [rule_id] + }) + |> json_response_and_validate_schema(200) + + assert %Activity{data: %{"rules" => [^rule_id]}} = Activity.get_report(id) + end + test "account_id is required", %{ conn: conn, activity: activity From d26aadb743c0de6fee7653ac2b90f989862d3c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 1 Mar 2022 23:50:09 +0100 Subject: [PATCH 0013/1008] Add tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- test/pleroma/rule_test.exs | 11 +++++++++++ test/pleroma/web/common_api_test.exs | 28 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/test/pleroma/rule_test.exs b/test/pleroma/rule_test.exs index 012ac902c..d710a6312 100644 --- a/test/pleroma/rule_test.exs +++ b/test/pleroma/rule_test.exs @@ -43,4 +43,15 @@ defmodule Pleroma.RuleTest do Rule.query() |> Pleroma.Repo.all() end + + test "getting rules by ids" do + %{id: id1} = Rule.create(%{text: "Example rule"}) + %{id: id2} = Rule.create(%{text: "Second rule"}) + %{id: _id3} = Rule.create(%{text: "Third rule"}) + + rules = Rule.get([id1, id2]) + + assert Enum.all?(rules, &(&1.id in [id1, id2])) + assert length(rules) == 2 + end end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index b502aaa03..103aab397 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -12,6 +12,7 @@ defmodule Pleroma.Web.CommonAPITest do alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.Rule alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier @@ -1186,6 +1187,33 @@ defmodule Pleroma.Web.CommonAPITest do assert first_report.data["state"] == "resolved" assert second_report.data["state"] == "resolved" end + + test "creates a report with provided rules" do + reporter = insert(:user) + target_user = insert(:user) + + %{id: rule_id} = Rule.create(%{text: "There are no rules"}) + + reporter_ap_id = reporter.ap_id + target_ap_id = target_user.ap_id + + report_data = %{ + account_id: target_user.id, + rule_ids: [rule_id] + } + + assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data) + + assert %Activity{ + actor: ^reporter_ap_id, + data: %{ + "type" => "Flag", + "object" => [^target_ap_id], + "state" => "open", + "rules" => [^rule_id] + } + } = flag_activity + end end describe "reblog muting" do From 5c383ada8ad7491125a8264a8a03d85fe822f61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Mar 2022 21:45:34 +0100 Subject: [PATCH 0014/1008] Correctly order rules by id/creation date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index 486cff8cc..b1db1dc0c 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Rule do def query do Rule |> order_by(asc: :priority) + |> order_by(asc: :id) end def get(ids) when is_list(ids) do From b354d70e85bbf0f685f3d56f7377fde2efce4187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 30 May 2022 12:30:03 +0200 Subject: [PATCH 0015/1008] Apply, suggestions, use strings for actual Mastodon API compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 2 ++ lib/pleroma/web/admin_api/views/report_view.ex | 10 ++++++---- lib/pleroma/web/admin_api/views/rule_view.ex | 2 +- .../operations/admin/report_operation.ex | 2 +- .../api_spec/operations/admin/rule_operation.ex | 5 ++--- .../api_spec/operations/instance_operation.ex | 2 +- .../web/api_spec/operations/report_operation.ex | 4 ++-- lib/pleroma/web/common_api.ex | 3 +-- .../web/mastodon_api/views/instance_view.ex | 2 +- .../controllers/rule_controller_test.exs | 4 ++++ .../controllers/report_controller_test.exs | 17 +++++++++++++++++ 11 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index b1db1dc0c..611e945b3 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -37,6 +37,8 @@ defmodule Pleroma.Rule do def get(id), do: Repo.get(__MODULE__, id) + def exists?(id), do: not is_nil(get(id)) + def create(params) do {:ok, rule} = %Rule{} diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index ca70f4359..b4b0be267 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do alias Pleroma.User alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report + alias Pleroma.Web.AdminAPI.RuleView alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.MastodonAPI.StatusView defdelegate merge_account_views(user), to: AdminAPI.AccountView @@ -80,8 +80,10 @@ defmodule Pleroma.Web.AdminAPI.ReportView do end defp rules(rule_ids) do - rule_ids - |> Rule.get() - |> render_many(InstanceView, "rule.json", as: :rule) + rules = + rule_ids + |> Rule.get() + + render(RuleView, "index.json", rules: rules) end end diff --git a/lib/pleroma/web/admin_api/views/rule_view.ex b/lib/pleroma/web/admin_api/views/rule_view.ex index f29145248..abfdd593f 100644 --- a/lib/pleroma/web/admin_api/views/rule_view.ex +++ b/lib/pleroma/web/admin_api/views/rule_view.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.AdminAPI.RuleView do def render("show.json", %{rule: rule} = _opts) do %{ - id: rule.id, + id: to_string(rule.id), priority: rule.priority, text: rule.text } diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index bb71abbd1..b90bbd592 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -175,7 +175,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do items: %Schema{ type: :object, properties: %{ - id: %Schema{type: :integer}, + id: %Schema{type: :string}, text: %Schema{type: :string} } } diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex index ed0d9eaf6..2360880e4 100644 --- a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -103,10 +103,9 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do %Schema{ type: :object, properties: %{ - id: %Schema{type: :integer}, + id: %Schema{type: :string}, priority: %Schema{type: :integer}, - text: %Schema{type: :string}, - created_at: %Schema{type: :string, format: :"date-time"} + text: %Schema{type: :string} } } end diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index e66e5b7a3..f3dba108e 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -191,7 +191,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do items: %Schema{ type: :object, properties: %{ - id: %Schema{type: :integer}, + id: %Schema{type: :string}, text: %Schema{type: :string} } } diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex index fd68f67a2..f5f88974c 100644 --- a/lib/pleroma/web/api_spec/operations/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -57,7 +57,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do rule_ids: %Schema{ type: :array, nullable: true, - items: %Schema{type: :number}, + items: %Schema{type: :string}, description: "Array of rules" } }, @@ -67,7 +67,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do "status_ids" => ["1337"], "comment" => "bad status!", "forward" => "false", - "rule_ids" => [3] + "rule_ids" => ["3"] } } end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 9f8d4def4..6fd744ddc 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -533,8 +533,7 @@ defmodule Pleroma.Web.CommonAPI do defp get_report_rules(rule_ids) do rule_ids - |> Rule.get() - |> Enum.map(& &1.id) + |> Enum.filter(&Rule.exists?/1) end def update_report_state(activity_ids, state) when is_list(activity_ids) do diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 8379731e4..c7f5ff554 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -66,7 +66,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do def render("rule.json", %{rule: rule}) do %{ - id: rule.id, + id: to_string(rule.id), text: rule.text } end diff --git a/test/pleroma/web/admin_api/controllers/rule_controller_test.exs b/test/pleroma/web/admin_api/controllers/rule_controller_test.exs index c5c72d293..96b52b272 100644 --- a/test/pleroma/web/admin_api/controllers/rule_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/rule_controller_test.exs @@ -27,6 +27,10 @@ defmodule Pleroma.Web.AdminAPI.RuleControllerTest do %{id: id2} = Rule.create(%{text: "Second rule", priority: 2}) %{id: id3} = Rule.create(%{text: "Third rule", priority: 1}) + id1 = to_string(id1) + id2 = to_string(id2) + id3 = to_string(id3) + response = conn |> get("/api/pleroma/admin/rules") diff --git a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs index 77626b821..689a7f375 100644 --- a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs @@ -65,6 +65,23 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do assert %Activity{data: %{"rules" => [^rule_id]}} = Activity.get_report(id) end + test "rules field is empty if provided wrong rule id", %{ + conn: conn, + target_user: target_user + } do + assert %{"id" => id} = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/reports", %{ + "account_id" => target_user.id, + "forward" => "false", + "rule_ids" => ["-1"] + }) + |> json_response_and_validate_schema(200) + + assert %Activity{data: %{"rules" => []}} = Activity.get_report(id) + end + test "account_id is required", %{ conn: conn, activity: activity From 0ecd6ba35e868eac296b013f743d82a120dd68db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 30 May 2022 12:50:44 +0200 Subject: [PATCH 0016/1008] AdminAPI: Allow filtering reports by rule_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/activity_pub/activity_pub.ex | 10 +++++++ .../operations/admin/report_operation.ex | 6 ++++ .../controllers/report_controller_test.exs | 29 +++++++++++++++++++ .../web/admin_api/views/report_view_test.exs | 8 +++-- .../controllers/report_controller_test.exs | 2 ++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 064f93b22..e54adf611 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1191,6 +1191,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_filtered(query, _), do: query + defp restrict_rule(query, %{rule_id: rule_id}) do + from( + activity in query, + where: fragment("(?)->'rules' \\? (?)", activity.data, ^rule_id) + ) + end + + defp restrict_rule(query, _), do: query + defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query defp exclude_poll_votes(query, _) do @@ -1353,6 +1362,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_instance(opts) |> restrict_announce_object_actor(opts) |> restrict_filtered(opts) + |> restrict_rule(opts) |> Activity.restrict_deactivated_users() |> exclude_poll_votes(opts) |> exclude_chat_messages(opts) diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index b90bbd592..18386296f 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -30,6 +30,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do report_state(), "Filter by report state" ), + Operation.parameter( + :rule_id, + :query, + %Schema{type: :string}, + "Filter by selected rule id" + ), Operation.parameter( :limit, :query, diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs index 30dcb87e2..5dc3e7491 100644 --- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs @@ -11,6 +11,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do alias Pleroma.ModerationLog alias Pleroma.Repo alias Pleroma.ReportNote + alias Pleroma.Rule alias Pleroma.Web.CommonAPI setup do @@ -313,6 +314,34 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do "error" => "Invalid credentials." } end + + test "returns reports with specified role_id", %{conn: conn} do + [reporter, target_user] = insert_pair(:user) + + %{id: rule_id} = Rule.create(%{text: "Example rule"}) + + rule_id = to_string(rule_id) + + {:ok, %{id: report_id}} = + CommonAPI.report(reporter, %{ + account_id: target_user.id, + comment: "", + rule_ids: [rule_id] + }) + + {:ok, _report} = + CommonAPI.report(reporter, %{ + account_id: target_user.id, + comment: "" + }) + + response = + conn + |> get("/api/pleroma/admin/reports?rule_id=#{rule_id}") + |> json_response_and_validate_schema(:ok) + + assert %{"reports" => [%{"id" => ^report_id}]} = response + end end describe "POST /api/pleroma/admin/reports/:id/notes" do diff --git a/test/pleroma/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs index 519208b45..1b16aca6a 100644 --- a/test/pleroma/web/admin_api/views/report_view_test.exs +++ b/test/pleroma/web/admin_api/views/report_view_test.exs @@ -176,15 +176,17 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do user = insert(:user) other_user = insert(:user) - %{id: id, text: text} = Rule.create(%{text: "Example rule"}) + %{id: rule_id, text: text} = Rule.create(%{text: "Example rule"}) + + rule_id = to_string(rule_id) {:ok, activity} = CommonAPI.report(user, %{ account_id: other_user.id, - rule_ids: [id] + rule_ids: [rule_id] }) - assert %{rules: [%{id: ^id, text: ^text}]} = + assert %{rules: [%{id: ^rule_id, text: ^text}]} = ReportView.render("show.json", Report.extract_report_info(activity)) end end diff --git a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs index 689a7f375..509de6899 100644 --- a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs @@ -52,6 +52,8 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do } do %{id: rule_id} = Rule.create(%{text: "There are no rules"}) + rule_id = to_string(rule_id) + assert %{"action_taken" => false, "id" => id} = conn |> put_req_header("content-type", "application/json") From 5846e7d5f6b91ab63270f2104543d874589d39ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 1 Jun 2022 16:03:22 +0200 Subject: [PATCH 0017/1008] Use Repo.exists? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index 611e945b3..c8e3470c7 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -37,7 +37,10 @@ defmodule Pleroma.Rule do def get(id), do: Repo.get(__MODULE__, id) - def exists?(id), do: not is_nil(get(id)) + def exists?(id) do + from(r in __MODULE__, where: r.id == ^id) + |> Repo.exists?() + end def create(params) do {:ok, rule} = From 6b74a5527410c5e98a6c4906a8e6f9c4d9f5e5d1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Jul 2022 15:40:40 -0500 Subject: [PATCH 0018/1008] InstanceStatic should have reasonable caching While here fix the naming convention of the old module attribute restricting caching and add a new one for the default cache value All frontends should be shipped with versioned assets. There be dragons if you don't. --- lib/pleroma/web/endpoint.ex | 41 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index d8d40cceb..17bd956d8 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Web.Plugs.HTTPSecurityPlug) plug(Pleroma.Web.Plugs.UploadedMedia) - @static_cache_control "public, no-cache" + @static_cache_control "public, max-age=1209600" + @static_cache_disabled "public, no-cache" # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well @@ -30,22 +31,32 @@ defmodule Pleroma.Web.Endpoint do from: :pleroma, only: ["emoji", "images"], gzip: true, - cache_control_for_etags: "public, max-age=1209600", - headers: %{ - "cache-control" => "public, max-age=1209600" - } - ) - - plug(Pleroma.Web.Plugs.InstanceStatic, - at: "/", - gzip: true, cache_control_for_etags: @static_cache_control, headers: %{ "cache-control" => @static_cache_control } ) - # Careful! No `only` restriction here, as we don't know what frontends contain. + plug(Pleroma.Web.Plugs.InstanceStatic, + at: "/", + gzip: true, + cache_control_for_etags: @static_cache_disabled, + headers: %{ + "cache-control" => @static_cache_disabled + } + ) + + plug(Pleroma.Web.Plugs.FrontendStatic, + at: "/", + frontend_type: :primary, + only: ["index.html"], + gzip: true, + cache_control_for_etags: @static_cache_disabled, + headers: %{ + "cache-control" => @static_cache_disabled + } + ) + plug(Pleroma.Web.Plugs.FrontendStatic, at: "/", frontend_type: :primary, @@ -62,9 +73,9 @@ defmodule Pleroma.Web.Endpoint do at: "/pleroma/admin", frontend_type: :admin, gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) @@ -79,9 +90,9 @@ defmodule Pleroma.Web.Endpoint do only: Pleroma.Constants.static_only_files(), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) From fa2a6d5d6b24657ddbda4ef11d2e6dbcb59545d3 Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Thu, 7 Apr 2022 18:25:02 +0200 Subject: [PATCH 0019/1008] feat: simple, but not stupid, uploader for IPFS fix: format fix with credo --- config/config.exs | 4 +++ config/description.exs | 24 +++++++++++++ config/dev.exs | 4 +++ lib/pleroma/upload.ex | 13 +++++-- lib/pleroma/uploaders/ipfs.ex | 64 +++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/uploaders/ipfs.ex diff --git a/config/config.exs b/config/config.exs index 6a5acda09..7efad0061 100644 --- a/config/config.exs +++ b/config/config.exs @@ -82,6 +82,10 @@ config :ex_aws, :s3, # region: "us-east-1", # may be required for Amazon AWS scheme: "https://" +config :pleroma, Pleroma.Uploaders.IPFS, + post_gateway_url: nil, + get_gateway_url: nil + config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"], pack_extensions: [".png", ".gif"], diff --git a/config/description.exs b/config/description.exs index 7caad18b4..d87bbb9b8 100644 --- a/config/description.exs +++ b/config/description.exs @@ -136,6 +136,30 @@ config :pleroma, :config_description, [ } ] }, + %{ + group: :pleroma, + key: Pleroma.Uploaders.IPFS, + type: :group, + description: "IPFS uploader-related settings", + children: [ + %{ + key: :get_gateway_url, + type: :string, + description: "GET Gateway URL", + suggestions: [ + "get_gateway_url" + ] + }, + %{ + key: :post_gateway_url, + type: :string, + description: "POST Gateway URL", + suggestions: [ + "post_gateway_url" + ] + } + ] + }, %{ group: :pleroma, key: Pleroma.Uploaders.S3, diff --git a/config/dev.exs b/config/dev.exs index ab3e83c12..89a84bf05 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -58,6 +58,10 @@ config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true # https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects config :phoenix, :plug_init_mode, :runtime +config :pleroma, Pleroma.Uploaders.IPFS, + post_gateway_url: nil, + get_gateway_url: nil + if File.exists?("./config/dev.secret.exs") do import_config "dev.secret.exs" else diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index db2909276..de39bcd6c 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -235,8 +235,14 @@ defmodule Pleroma.Upload do "" end - [base_url, path] - |> Path.join() + uploader = Config.get([Pleroma.Upload, :uploader]) + + if uploader == Pleroma.Uploaders.IPFS && String.contains?(base_url, "{CID}") do + String.replace(base_url, "{CID}", path) + else + [base_url, path] + |> Path.join() + end end defp url_from_spec(_upload, _base_url, {:url, url}), do: url @@ -273,6 +279,9 @@ defmodule Pleroma.Upload do Path.join([upload_base_url, bucket_with_namespace]) end + Pleroma.Uploaders.IPFS -> + Config.get([Pleroma.Uploaders.IPFS, :get_gateway_url]) + _ -> public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/" end diff --git a/lib/pleroma/uploaders/ipfs.ex b/lib/pleroma/uploaders/ipfs.ex new file mode 100644 index 000000000..b46e9322e --- /dev/null +++ b/lib/pleroma/uploaders/ipfs.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Uploaders.IPFS do + @behaviour Pleroma.Uploaders.Uploader + require Logger + + alias Pleroma.Config + alias Tesla.Multipart + + @impl true + def get_file(file) do + b_url = Pleroma.Upload.base_url() + + if String.contains?(b_url, "{CID}") do + {:ok, {:url, String.replace(b_url, "{CID}", URI.decode(file))}} + else + {:error, "IPFS Get URL doesn't contain '{CID}' placeholder"} + end + end + + @impl true + def put_file(%Pleroma.Upload{} = upload) do + config = Config.get([__MODULE__]) + post_base_url = Keyword.get(config, :post_gateway_url) + + mp = + Multipart.new() + |> Multipart.add_content_type_param("charset=utf-8") + |> Multipart.add_file(upload.tempfile) + + final_url = Path.join([post_base_url, "/api/v0/add"]) + + case Pleroma.HTTP.post(final_url, mp, [], params: ["cid-version": "1"]) do + {:ok, ret} -> + case Jason.decode(ret.body) do + {:ok, ret} -> + {:ok, {:file, ret["Hash"]}} + + error -> + Logger.error("#{__MODULE__}: #{inspect(error)}") + {:error, "JSON decode failed"} + end + + error -> + Logger.error("#{__MODULE__}: #{inspect(error)}") + {:error, "IPFS Gateway Upload failed"} + end + end + + @impl true + def delete_file(file) do + config = Config.get([__MODULE__]) + post_base_url = Keyword.get(config, :post_gateway_url) + + final_url = Path.join([post_base_url, "/api/v0/files/rm"]) + + case Pleroma.HTTP.post(final_url, "", [], params: [arg: file]) do + {:ok, %{status_code: 204}} -> :ok + error -> {:error, inspect(error)} + end + end +end From 43dfa58ebda407a0813d398bee8d0ae3e5c9fd5b Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Mon, 11 Apr 2022 15:10:01 +0200 Subject: [PATCH 0020/1008] added tests for ipfs uploader. adapted changelog.md accordingly. improved ipfs uploader with external suggestions fix lint description.exs --- CHANGELOG.md | 1 + config/description.exs | 5 +- config/dev.exs | 4 -- docs/configuration/cheatsheet.md | 13 ++++ lib/pleroma/upload.ex | 6 +- lib/pleroma/uploaders/ipfs.ex | 14 +++-- test/pleroma/uploaders/ipfs_test.exs | 88 ++++++++++++++++++++++++++++ 7 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 test/pleroma/uploaders/ipfs_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index f1beb0cd0..b2185b1ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - PleromaAPI: Add `GET /api/v1/pleroma/birthdays` API endpoint - Make backend-rendered pages translatable. This includes emails. Pages returned as a HTTP response are translated using the language specified in the `userLanguage` cookie, or the `Accept-Language` header. Emails are translated using the `language` field when registering. This language can be changed by `PATCH /api/v1/accounts/update_credentials` with the `language` field. - Uploadfilter `Pleroma.Upload.Filter.Exiftool.ReadDescription` returns description values to the FE so they can pre fill the image description field +- Uploader: Add support for uploading attachments using IPFS ### Fixed - Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies diff --git a/config/description.exs b/config/description.exs index d87bbb9b8..1fe5f01f0 100644 --- a/config/description.exs +++ b/config/description.exs @@ -147,7 +147,8 @@ config :pleroma, :config_description, [ type: :string, description: "GET Gateway URL", suggestions: [ - "get_gateway_url" + "https://ipfs.mydomain.com/<%= cid %>", + "https://<%= cid %>.ipfs.mydomain.com/" ] }, %{ @@ -155,7 +156,7 @@ config :pleroma, :config_description, [ type: :string, description: "POST Gateway URL", suggestions: [ - "post_gateway_url" + "http://localhost:5001/" ] } ] diff --git a/config/dev.exs b/config/dev.exs index 89a84bf05..ab3e83c12 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -58,10 +58,6 @@ config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true # https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects config :phoenix, :plug_init_mode, :runtime -config :pleroma, Pleroma.Uploaders.IPFS, - post_gateway_url: nil, - get_gateway_url: nil - if File.exists?("./config/dev.secret.exs") do import_config "dev.secret.exs" else diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 74642397b..7e1f9c934 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -614,6 +614,19 @@ config :ex_aws, :s3, host: "s3.eu-central-1.amazonaws.com" ``` +#### Pleroma.Uploaders.IPFS + +* `post_gateway_url`: URL with port of POST Gateway (unauthenticated) +* `get_gateway_url`: URL of public GET Gateway + +Example: + +```elixir +config :pleroma, Pleroma.Uploaders.IPFS, + post_gateway_url: "http://localhost:5001", + get_gateway_url: "http://<%= cid %>.ipfs.mydomain.com" +``` + ### Upload filters #### Pleroma.Upload.Filter.AnonymizeFilename diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index de39bcd6c..b51d23f9e 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -235,10 +235,8 @@ defmodule Pleroma.Upload do "" end - uploader = Config.get([Pleroma.Upload, :uploader]) - - if uploader == Pleroma.Uploaders.IPFS && String.contains?(base_url, "{CID}") do - String.replace(base_url, "{CID}", path) + if String.contains?(base_url, "<%= cid %>") do + EEx.eval_string(base_url, cid: path) else [base_url, path] |> Path.join() diff --git a/lib/pleroma/uploaders/ipfs.ex b/lib/pleroma/uploaders/ipfs.ex index b46e9322e..722c68fa1 100644 --- a/lib/pleroma/uploaders/ipfs.ex +++ b/lib/pleroma/uploaders/ipfs.ex @@ -13,10 +13,10 @@ defmodule Pleroma.Uploaders.IPFS do def get_file(file) do b_url = Pleroma.Upload.base_url() - if String.contains?(b_url, "{CID}") do - {:ok, {:url, String.replace(b_url, "{CID}", URI.decode(file))}} + if String.contains?(b_url, "<%= cid %>") do + {:ok, {:url, EEx.eval_string(b_url, cid: URI.decode(file))}} else - {:error, "IPFS Get URL doesn't contain '{CID}' placeholder"} + {:error, "IPFS Get URL doesn't contain 'cid' placeholder"} end end @@ -36,7 +36,11 @@ defmodule Pleroma.Uploaders.IPFS do {:ok, ret} -> case Jason.decode(ret.body) do {:ok, ret} -> - {:ok, {:file, ret["Hash"]}} + if Map.has_key?(ret, "Hash") do + {:ok, {:file, ret["Hash"]}} + else + {:error, "JSON doesn't contain Hash value"} + end error -> Logger.error("#{__MODULE__}: #{inspect(error)}") @@ -45,7 +49,7 @@ defmodule Pleroma.Uploaders.IPFS do error -> Logger.error("#{__MODULE__}: #{inspect(error)}") - {:error, "IPFS Gateway Upload failed"} + {:error, "IPFS Gateway upload failed"} end end diff --git a/test/pleroma/uploaders/ipfs_test.exs b/test/pleroma/uploaders/ipfs_test.exs new file mode 100644 index 000000000..f9ae046cf --- /dev/null +++ b/test/pleroma/uploaders/ipfs_test.exs @@ -0,0 +1,88 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Uploaders.IPFSTest do + use Pleroma.DataCase + + alias Pleroma.Uploaders.IPFS + alias Tesla.Multipart + + import Mock + import ExUnit.CaptureLog + + setup do + clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.IPFS) + clear_config([Pleroma.Uploaders.IPFS]) + + clear_config( + [Pleroma.Uploaders.IPFS, :get_gateway_url], + "https://<%= cid %>.ipfs.mydomain.com" + ) + + clear_config([Pleroma.Uploaders.IPFS, :post_gateway_url], "http://localhost:5001") + end + + describe "get_file/1" do + test "it returns path to ipfs file with cid as subdomain" do + assert IPFS.get_file("testcid") == { + :ok, + {:url, "https://testcid.ipfs.mydomain.com"} + } + end + + test "it returns path to ipfs file with cid as path" do + clear_config( + [Pleroma.Uploaders.IPFS, :get_gateway_url], + "https://ipfs.mydomain.com/ipfs/<%= cid %>" + ) + + assert IPFS.get_file("testcid") == { + :ok, + {:url, "https://ipfs.mydomain.com/ipfs/testcid"} + } + end + end + + describe "put_file/1" do + setup do + file_upload = %Pleroma.Upload{ + name: "image-tet.jpg", + content_type: "image/jpeg", + path: "test_folder/image-tet.jpg", + tempfile: Path.absname("test/instance_static/add/shortcode.png") + } + + [file_upload: file_upload] + end + + test "save file", %{file_upload: file_upload} do + with_mock Pleroma.HTTP, + post: fn _, _, _, _ -> + {:ok, + %Tesla.Env{ + status: 200, + body: "{\"Hash\":\"bafybeicrh7ltzx52yxcwrvxxckfmwhqdgsb6qym6dxqm2a4ymsakeshwoi\"}" + }} + end do + assert IPFS.put_file(file_upload) == + {:ok, {:file, "bafybeicrh7ltzx52yxcwrvxxckfmwhqdgsb6qym6dxqm2a4ymsakeshwoi"}} + end + end + + test "returns error", %{file_upload: file_upload} do + with_mock Pleroma.HTTP, post: fn _, _, _, _ -> {:error, "IPFS Gateway upload failed"} end do + assert capture_log(fn -> + assert IPFS.put_file(file_upload) == {:error, "IPFS Gateway upload failed"} + end) =~ "Elixir.Pleroma.Uploaders.IPFS: {:error, \"IPFS Gateway upload failed\"}" + end + end + end + + describe "delete_file/1" do + test_with_mock "deletes file", Pleroma.HTTP, + post: fn _, _, _, _ -> {:ok, %{status_code: 204}} end do + assert :ok = IPFS.delete_file("image.jpg") + end + end +end From 44659ecd65fb2251f9130fcecf1732b8931104c1 Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Sat, 16 Apr 2022 09:38:49 +0200 Subject: [PATCH 0021/1008] ipfs: revert to String.replace for cid placeholder ipfs: fix lint --- config/description.exs | 4 ++-- docs/configuration/cheatsheet.md | 2 +- lib/pleroma/upload.ex | 4 ++-- lib/pleroma/uploaders/ipfs.ex | 7 +++++-- test/pleroma/uploaders/ipfs_test.exs | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/config/description.exs b/config/description.exs index 1fe5f01f0..b180e7308 100644 --- a/config/description.exs +++ b/config/description.exs @@ -147,8 +147,8 @@ config :pleroma, :config_description, [ type: :string, description: "GET Gateway URL", suggestions: [ - "https://ipfs.mydomain.com/<%= cid %>", - "https://<%= cid %>.ipfs.mydomain.com/" + "https://ipfs.mydomain.com/{CID}", + "https://{CID}.ipfs.mydomain.com/" ] }, %{ diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 7e1f9c934..d35b33574 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -624,7 +624,7 @@ Example: ```elixir config :pleroma, Pleroma.Uploaders.IPFS, post_gateway_url: "http://localhost:5001", - get_gateway_url: "http://<%= cid %>.ipfs.mydomain.com" + get_gateway_url: "http://{CID}.ipfs.mydomain.com" ``` ### Upload filters diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index b51d23f9e..8a01cf613 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -235,8 +235,8 @@ defmodule Pleroma.Upload do "" end - if String.contains?(base_url, "<%= cid %>") do - EEx.eval_string(base_url, cid: path) + if String.contains?(base_url, Pleroma.Uploaders.IPFS.placeholder()) do + String.replace(base_url, Pleroma.Uploaders.IPFS.placeholder(), path) else [base_url, path] |> Path.join() diff --git a/lib/pleroma/uploaders/ipfs.ex b/lib/pleroma/uploaders/ipfs.ex index 722c68fa1..dde520d8e 100644 --- a/lib/pleroma/uploaders/ipfs.ex +++ b/lib/pleroma/uploaders/ipfs.ex @@ -9,12 +9,15 @@ defmodule Pleroma.Uploaders.IPFS do alias Pleroma.Config alias Tesla.Multipart + @placeholder "{CID}" + def placeholder, do: @placeholder + @impl true def get_file(file) do b_url = Pleroma.Upload.base_url() - if String.contains?(b_url, "<%= cid %>") do - {:ok, {:url, EEx.eval_string(b_url, cid: URI.decode(file))}} + if String.contains?(b_url, @placeholder) do + {:ok, {:url, String.replace(b_url, @placeholder, URI.decode(file))}} else {:error, "IPFS Get URL doesn't contain 'cid' placeholder"} end diff --git a/test/pleroma/uploaders/ipfs_test.exs b/test/pleroma/uploaders/ipfs_test.exs index f9ae046cf..fc87fa378 100644 --- a/test/pleroma/uploaders/ipfs_test.exs +++ b/test/pleroma/uploaders/ipfs_test.exs @@ -17,7 +17,7 @@ defmodule Pleroma.Uploaders.IPFSTest do clear_config( [Pleroma.Uploaders.IPFS, :get_gateway_url], - "https://<%= cid %>.ipfs.mydomain.com" + "https://{CID}.ipfs.mydomain.com" ) clear_config([Pleroma.Uploaders.IPFS, :post_gateway_url], "http://localhost:5001") @@ -34,7 +34,7 @@ defmodule Pleroma.Uploaders.IPFSTest do test "it returns path to ipfs file with cid as path" do clear_config( [Pleroma.Uploaders.IPFS, :get_gateway_url], - "https://ipfs.mydomain.com/ipfs/<%= cid %>" + "https://ipfs.mydomain.com/ipfs/{CID}" ) assert IPFS.get_file("testcid") == { From 7c1af86f979ecebcd38995e5278fe2d59a36eda5 Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Mon, 9 May 2022 12:15:40 +0200 Subject: [PATCH 0022/1008] ipfs: refactor final_url generation. add tests for final_url fix lint --- lib/pleroma/uploaders/ipfs.ex | 19 ++++++++++--------- test/pleroma/uploaders/ipfs_test.exs | 13 ++++++++++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/uploaders/ipfs.ex b/lib/pleroma/uploaders/ipfs.ex index dde520d8e..7a7481d81 100644 --- a/lib/pleroma/uploaders/ipfs.ex +++ b/lib/pleroma/uploaders/ipfs.ex @@ -12,6 +12,13 @@ defmodule Pleroma.Uploaders.IPFS do @placeholder "{CID}" def placeholder, do: @placeholder + def get_final_url(method) do + config = Config.get([__MODULE__]) + post_base_url = Keyword.get(config, :post_gateway_url) + + Path.join([post_base_url, method]) + end + @impl true def get_file(file) do b_url = Pleroma.Upload.base_url() @@ -25,15 +32,12 @@ defmodule Pleroma.Uploaders.IPFS do @impl true def put_file(%Pleroma.Upload{} = upload) do - config = Config.get([__MODULE__]) - post_base_url = Keyword.get(config, :post_gateway_url) - mp = Multipart.new() |> Multipart.add_content_type_param("charset=utf-8") |> Multipart.add_file(upload.tempfile) - final_url = Path.join([post_base_url, "/api/v0/add"]) + final_url = get_final_url("/api/v0/add") case Pleroma.HTTP.post(final_url, mp, [], params: ["cid-version": "1"]) do {:ok, ret} -> @@ -42,7 +46,7 @@ defmodule Pleroma.Uploaders.IPFS do if Map.has_key?(ret, "Hash") do {:ok, {:file, ret["Hash"]}} else - {:error, "JSON doesn't contain Hash value"} + {:error, "JSON doesn't contain Hash key"} end error -> @@ -58,10 +62,7 @@ defmodule Pleroma.Uploaders.IPFS do @impl true def delete_file(file) do - config = Config.get([__MODULE__]) - post_base_url = Keyword.get(config, :post_gateway_url) - - final_url = Path.join([post_base_url, "/api/v0/files/rm"]) + final_url = get_final_url("/api/v0/files/rm") case Pleroma.HTTP.post(final_url, "", [], params: [arg: file]) do {:ok, %{status_code: 204}} -> :ok diff --git a/test/pleroma/uploaders/ipfs_test.exs b/test/pleroma/uploaders/ipfs_test.exs index fc87fa378..d567272d2 100644 --- a/test/pleroma/uploaders/ipfs_test.exs +++ b/test/pleroma/uploaders/ipfs_test.exs @@ -23,6 +23,16 @@ defmodule Pleroma.Uploaders.IPFSTest do clear_config([Pleroma.Uploaders.IPFS, :post_gateway_url], "http://localhost:5001") end + describe "get_final_url" do + test "it returns the final url for put_file" do + assert IPFS.get_final_url("/api/v0/add") == "http://localhost:5001/api/v0/add" + end + + test "it returns the final url for delete_file" do + assert IPFS.get_final_url("/api/v0/files/rm") == "http://localhost:5001/api/v0/files/rm" + end + end + describe "get_file/1" do test "it returns path to ipfs file with cid as subdomain" do assert IPFS.get_file("testcid") == { @@ -62,7 +72,8 @@ defmodule Pleroma.Uploaders.IPFSTest do {:ok, %Tesla.Env{ status: 200, - body: "{\"Hash\":\"bafybeicrh7ltzx52yxcwrvxxckfmwhqdgsb6qym6dxqm2a4ymsakeshwoi\"}" + body: + "{\"Name\":\"image-tet.jpg\",\"Size\":\"5000\", \"Hash\":\"bafybeicrh7ltzx52yxcwrvxxckfmwhqdgsb6qym6dxqm2a4ymsakeshwoi\"}" }} end do assert IPFS.put_file(file_upload) == From 98f268e5ecc5bab98c98270a582f8b3f0e3be4e8 Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Thu, 9 Jun 2022 19:24:13 +0200 Subject: [PATCH 0023/1008] ipfs: small refactor and more tests --- lib/pleroma/uploaders/ipfs.ex | 24 ++++++++++++++---------- test/pleroma/uploaders/ipfs_test.exs | 24 ++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/uploaders/ipfs.ex b/lib/pleroma/uploaders/ipfs.ex index 7a7481d81..9f6f26e2e 100644 --- a/lib/pleroma/uploaders/ipfs.ex +++ b/lib/pleroma/uploaders/ipfs.ex @@ -9,16 +9,24 @@ defmodule Pleroma.Uploaders.IPFS do alias Pleroma.Config alias Tesla.Multipart - @placeholder "{CID}" - def placeholder, do: @placeholder - - def get_final_url(method) do + defp get_final_url(method) do config = Config.get([__MODULE__]) post_base_url = Keyword.get(config, :post_gateway_url) Path.join([post_base_url, method]) end + def put_file_endpoint() do + get_final_url("/api/v0/add") + end + + def delete_file_endpoint() do + get_final_url("/api/v0/files/rm") + end + + @placeholder "{CID}" + def placeholder, do: @placeholder + @impl true def get_file(file) do b_url = Pleroma.Upload.base_url() @@ -37,9 +45,7 @@ defmodule Pleroma.Uploaders.IPFS do |> Multipart.add_content_type_param("charset=utf-8") |> Multipart.add_file(upload.tempfile) - final_url = get_final_url("/api/v0/add") - - case Pleroma.HTTP.post(final_url, mp, [], params: ["cid-version": "1"]) do + case Pleroma.HTTP.post(put_file_endpoint(), mp, [], params: ["cid-version": "1"]) do {:ok, ret} -> case Jason.decode(ret.body) do {:ok, ret} -> @@ -62,9 +68,7 @@ defmodule Pleroma.Uploaders.IPFS do @impl true def delete_file(file) do - final_url = get_final_url("/api/v0/files/rm") - - case Pleroma.HTTP.post(final_url, "", [], params: [arg: file]) do + case Pleroma.HTTP.post(delete_file_endpoint(), "", [], params: [arg: file]) do {:ok, %{status_code: 204}} -> :ok error -> {:error, inspect(error)} end diff --git a/test/pleroma/uploaders/ipfs_test.exs b/test/pleroma/uploaders/ipfs_test.exs index d567272d2..f2b880d9f 100644 --- a/test/pleroma/uploaders/ipfs_test.exs +++ b/test/pleroma/uploaders/ipfs_test.exs @@ -25,11 +25,11 @@ defmodule Pleroma.Uploaders.IPFSTest do describe "get_final_url" do test "it returns the final url for put_file" do - assert IPFS.get_final_url("/api/v0/add") == "http://localhost:5001/api/v0/add" + assert IPFS.put_file_endpoint() == "http://localhost:5001/api/v0/add" end test "it returns the final url for delete_file" do - assert IPFS.get_final_url("/api/v0/files/rm") == "http://localhost:5001/api/v0/files/rm" + assert IPFS.delete_file_endpoint() == "http://localhost:5001/api/v0/files/rm" end end @@ -88,6 +88,26 @@ defmodule Pleroma.Uploaders.IPFSTest do end) =~ "Elixir.Pleroma.Uploaders.IPFS: {:error, \"IPFS Gateway upload failed\"}" end end + + test "returns error if JSON decode fails", %{file_upload: file_upload} do + with_mocks([ + {Pleroma.HTTP, [], [post: fn _, _, _, _ -> {:ok, %Tesla.Env{status: 200, body: ''}} end]}, + {Jason, [], [decode: fn _ -> {:error, "JSON decode failed"} end]} + ]) do + assert capture_log(fn -> + assert IPFS.put_file(file_upload) == {:error, "JSON decode failed"} + end) =~ "Elixir.Pleroma.Uploaders.IPFS: {:error, \"JSON decode failed\"}" + end + end + + test "returns error if JSON body doesn't contain Hash key", %{file_upload: file_upload} do + with_mocks([ + {Pleroma.HTTP, [], [post: fn _, _, _, _ -> {:ok, %Tesla.Env{status: 200, body: ''}} end]}, + {Jason, [], [decode: fn _ -> {:ok, %{}} end]} + ]) do + assert IPFS.put_file(file_upload) == {:error, "JSON doesn't contain Hash key"} + end + end end describe "delete_file/1" do From 254f2ea85400ebd692fc4a45f5ac22fedd49ec09 Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Thu, 9 Jun 2022 23:38:50 +0200 Subject: [PATCH 0024/1008] ipfs: remove unused alias fix analysis job --- lib/pleroma/uploaders/ipfs.ex | 4 ++-- test/pleroma/uploaders/ipfs_test.exs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/uploaders/ipfs.ex b/lib/pleroma/uploaders/ipfs.ex index 9f6f26e2e..32e06c5cf 100644 --- a/lib/pleroma/uploaders/ipfs.ex +++ b/lib/pleroma/uploaders/ipfs.ex @@ -16,11 +16,11 @@ defmodule Pleroma.Uploaders.IPFS do Path.join([post_base_url, method]) end - def put_file_endpoint() do + def put_file_endpoint do get_final_url("/api/v0/add") end - def delete_file_endpoint() do + def delete_file_endpoint do get_final_url("/api/v0/files/rm") end diff --git a/test/pleroma/uploaders/ipfs_test.exs b/test/pleroma/uploaders/ipfs_test.exs index f2b880d9f..5edb6266b 100644 --- a/test/pleroma/uploaders/ipfs_test.exs +++ b/test/pleroma/uploaders/ipfs_test.exs @@ -6,7 +6,6 @@ defmodule Pleroma.Uploaders.IPFSTest do use Pleroma.DataCase alias Pleroma.Uploaders.IPFS - alias Tesla.Multipart import Mock import ExUnit.CaptureLog From 5e097eb91def0efd3cd0008309fd524fcfd88e15 Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Tue, 28 Jun 2022 17:53:44 +0200 Subject: [PATCH 0025/1008] ipfs: better tests with @ilja suggestions --- test/pleroma/uploaders/ipfs_test.exs | 38 ++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/test/pleroma/uploaders/ipfs_test.exs b/test/pleroma/uploaders/ipfs_test.exs index 5edb6266b..9df3f239c 100644 --- a/test/pleroma/uploaders/ipfs_test.exs +++ b/test/pleroma/uploaders/ipfs_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Uploaders.IPFSTest do use Pleroma.DataCase alias Pleroma.Uploaders.IPFS + alias Tesla.Multipart import Mock import ExUnit.CaptureLog @@ -62,12 +63,17 @@ defmodule Pleroma.Uploaders.IPFSTest do tempfile: Path.absname("test/instance_static/add/shortcode.png") } - [file_upload: file_upload] + mp = + Multipart.new() + |> Multipart.add_content_type_param("charset=utf-8") + |> Multipart.add_file(file_upload.tempfile) + + [file_upload: file_upload, mp: mp] end test "save file", %{file_upload: file_upload} do with_mock Pleroma.HTTP, - post: fn _, _, _, _ -> + post: fn "http://localhost:5001/api/v0/add", mp, [], params: ["cid-version": "1"] -> {:ok, %Tesla.Env{ status: 200, @@ -81,7 +87,10 @@ defmodule Pleroma.Uploaders.IPFSTest do end test "returns error", %{file_upload: file_upload} do - with_mock Pleroma.HTTP, post: fn _, _, _, _ -> {:error, "IPFS Gateway upload failed"} end do + with_mock Pleroma.HTTP, + post: fn "http://localhost:5001/api/v0/add", mp, [], params: ["cid-version": "1"] -> + {:error, "IPFS Gateway upload failed"} + end do assert capture_log(fn -> assert IPFS.put_file(file_upload) == {:error, "IPFS Gateway upload failed"} end) =~ "Elixir.Pleroma.Uploaders.IPFS: {:error, \"IPFS Gateway upload failed\"}" @@ -89,21 +98,22 @@ defmodule Pleroma.Uploaders.IPFSTest do end test "returns error if JSON decode fails", %{file_upload: file_upload} do - with_mocks([ - {Pleroma.HTTP, [], [post: fn _, _, _, _ -> {:ok, %Tesla.Env{status: 200, body: ''}} end]}, - {Jason, [], [decode: fn _ -> {:error, "JSON decode failed"} end]} - ]) do + with_mock Pleroma.HTTP, [], + post: fn "http://localhost:5001/api/v0/add", mp, [], params: ["cid-version": "1"] -> + {:ok, %Tesla.Env{status: 200, body: 'invalid'}} + end do assert capture_log(fn -> assert IPFS.put_file(file_upload) == {:error, "JSON decode failed"} - end) =~ "Elixir.Pleroma.Uploaders.IPFS: {:error, \"JSON decode failed\"}" + end) =~ + "Elixir.Pleroma.Uploaders.IPFS: {:error, %Jason.DecodeError{data: \"invalid\", position: 0, token: nil}}" end end test "returns error if JSON body doesn't contain Hash key", %{file_upload: file_upload} do - with_mocks([ - {Pleroma.HTTP, [], [post: fn _, _, _, _ -> {:ok, %Tesla.Env{status: 200, body: ''}} end]}, - {Jason, [], [decode: fn _ -> {:ok, %{}} end]} - ]) do + with_mock Pleroma.HTTP, [], + post: fn "http://localhost:5001/api/v0/add", mp, [], params: ["cid-version": "1"] -> + {:ok, %Tesla.Env{status: 200, body: '{"key": "value"}'}} + end do assert IPFS.put_file(file_upload) == {:error, "JSON doesn't contain Hash key"} end end @@ -111,7 +121,9 @@ defmodule Pleroma.Uploaders.IPFSTest do describe "delete_file/1" do test_with_mock "deletes file", Pleroma.HTTP, - post: fn _, _, _, _ -> {:ok, %{status_code: 204}} end do + post: fn "http://localhost:5001/api/v0/files/rm", "", [], params: [arg: "image.jpg"] -> + {:ok, %{status_code: 204}} + end do assert :ok = IPFS.delete_file("image.jpg") end end From 21d9091f5e422493ff69fe59db9c965e0d511369 Mon Sep 17 00:00:00 2001 From: Claudio Maradonna Date: Fri, 8 Jul 2022 10:06:46 +0200 Subject: [PATCH 0026/1008] ipfs: replacing single quotes with double quotes --- test/pleroma/uploaders/ipfs_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/pleroma/uploaders/ipfs_test.exs b/test/pleroma/uploaders/ipfs_test.exs index 9df3f239c..853d185e5 100644 --- a/test/pleroma/uploaders/ipfs_test.exs +++ b/test/pleroma/uploaders/ipfs_test.exs @@ -100,7 +100,7 @@ defmodule Pleroma.Uploaders.IPFSTest do test "returns error if JSON decode fails", %{file_upload: file_upload} do with_mock Pleroma.HTTP, [], post: fn "http://localhost:5001/api/v0/add", mp, [], params: ["cid-version": "1"] -> - {:ok, %Tesla.Env{status: 200, body: 'invalid'}} + {:ok, %Tesla.Env{status: 200, body: "invalid"}} end do assert capture_log(fn -> assert IPFS.put_file(file_upload) == {:error, "JSON decode failed"} @@ -112,7 +112,7 @@ defmodule Pleroma.Uploaders.IPFSTest do test "returns error if JSON body doesn't contain Hash key", %{file_upload: file_upload} do with_mock Pleroma.HTTP, [], post: fn "http://localhost:5001/api/v0/add", mp, [], params: ["cid-version": "1"] -> - {:ok, %Tesla.Env{status: 200, body: '{"key": "value"}'}} + {:ok, %Tesla.Env{status: 200, body: "{\"key\": \"value\"}"}} end do assert IPFS.put_file(file_upload) == {:error, "JSON doesn't contain Hash key"} end From 3ed39e310939d90ddbad7bd7ffa1ebd8aca6e74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 8 Jul 2022 21:28:23 +0200 Subject: [PATCH 0027/1008] Add test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- test/pleroma/notification_test.exs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index eea2fcb67..c43502eb5 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -128,6 +128,21 @@ defmodule Pleroma.NotificationTest do subscriber_notifications = Notification.for_user(subscriber) assert Enum.empty?(subscriber_notifications) end + + test "does not create subscriber notification if mentioned" do + user = insert(:user) + subscriber = insert(:user) + + User.subscribe(subscriber, user) + + {:ok, status} = CommonAPI.post(user, %{status: "mentioning @#{subscriber.nickname}"}) + {:ok, [notification] = notifications} = Notification.create_notifications(status) + + assert length(notifications) == 1 + + assert notification.user_id == subscriber.id + assert notification.type == "mention" + end end test "create_poll_notifications/1" do From 50d3209ce85c164acc374d236e6407c58f8ffd35 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 28 Aug 2022 09:48:01 -0400 Subject: [PATCH 0028/1008] Check for changelog in ci --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0e7f4926a..2fc46f976 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,6 +16,7 @@ cache: &global_cache_policy - _build stages: + - check-changelog - build - test - benchmark @@ -31,6 +32,13 @@ before_script: after_script: - rm -rf _build/*/lib/pleroma +check-changelog: + stage: check-changelog + rules: + - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" + script: + - count=0; for i in changelog.d/"$CI_MERGE_REQUEST_IID".{add,remove,fix,security}; do [ -f "$i" ]; count=$(( $count + 1 - $? )); done; [ $count -eq 1 ] + build: stage: build only: From 27a8f6a8d9a1825d3669d2d55add47e0c3e58445 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 28 Aug 2022 09:54:28 -0400 Subject: [PATCH 0029/1008] Prevent duplicate pipelines --- .gitlab-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2fc46f976..f6220b2d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,13 @@ variables: &global_variables DB_HOST: postgres MIX_ENV: test +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + cache: &global_cache_policy key: files: From d3871fa3600070f909eba61699c6e36d68f8de4d Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 28 Aug 2022 09:57:32 -0400 Subject: [PATCH 0030/1008] Allow to explicitly skip changelog --- .gitlab-ci.yml | 2 +- changelog.d/3739.skip | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/3739.skip diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f6220b2d9..24805bd4a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -44,7 +44,7 @@ check-changelog: rules: - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" script: - - count=0; for i in changelog.d/"$CI_MERGE_REQUEST_IID".{add,remove,fix,security}; do [ -f "$i" ]; count=$(( $count + 1 - $? )); done; [ $count -eq 1 ] + - count=0; for i in changelog.d/"$CI_MERGE_REQUEST_IID".{add,remove,fix,security,skip}; do [ -f "$i" ]; count=$(( $count + 1 - $? )); done; [ $count -eq 1 ] build: stage: build diff --git a/changelog.d/3739.skip b/changelog.d/3739.skip new file mode 100644 index 000000000..e69de29bb From a26fb6ab484eee18916765125c5dc9c49cc350a6 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 28 Aug 2022 10:24:01 -0400 Subject: [PATCH 0031/1008] Display error info --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 24805bd4a..bd6a5b82f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -44,7 +44,8 @@ check-changelog: rules: - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" script: - - count=0; for i in changelog.d/"$CI_MERGE_REQUEST_IID".{add,remove,fix,security,skip}; do [ -f "$i" ]; count=$(( $count + 1 - $? )); done; [ $count -eq 1 ] + - echo $CI_MERGE_REQUEST_IID + - count=0; for i in changelog.d/"$CI_MERGE_REQUEST_IID".{add,remove,fix,security,skip}; do [ -f "$i" ]; count=$(( $count + 1 - $? )); done; if [ $count -eq 1 ]; then echo "ok"; else echo "must have a changelog entry or explicitly skip it"; exit 1; fi build: stage: build From f8566e91a69571ae0b9d49867c844eeb575786f0 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 28 Aug 2022 10:35:53 -0400 Subject: [PATCH 0032/1008] Fix {} not working with alpine sh --- .gitlab-ci.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bd6a5b82f..47918a538 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,11 +41,33 @@ after_script: check-changelog: stage: check-changelog + image: alpine rules: - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" + before_script: '' + after_script: '' + cache: {} script: - echo $CI_MERGE_REQUEST_IID - - count=0; for i in changelog.d/"$CI_MERGE_REQUEST_IID".{add,remove,fix,security,skip}; do [ -f "$i" ]; count=$(( $count + 1 - $? )); done; if [ $count -eq 1 ]; then echo "ok"; else echo "must have a changelog entry or explicitly skip it"; exit 1; fi + - > + ls changelog.d + count=0 + for i in add remove fix security skip; do + [ -f changelog.d/"$CI_MERGE_REQUEST_IID"."$i" ] + retcode=$? + if [ $retcode -eq 0 ]; then + echo "found $CI_MERGE_REQUEST_IID.$i" + else + echo "no $CI_MERGE_REQUEST_IID.$i" + fi + count=$(( $count + 1 - $retcode )) + done + if [ $count -eq 1 ]; then + echo "ok" + else + echo "must have a changelog entry or explicitly skip it" + exit 1 + fi build: stage: build From 6aa9b023f04e3151c85591ac02796c4536fa7958 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sun, 28 Aug 2022 11:13:36 -0400 Subject: [PATCH 0033/1008] Use dedicated script --- .gitlab-ci.yml | 21 +-------------------- tools/check-changelog | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 20 deletions(-) create mode 100644 tools/check-changelog diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 47918a538..f75dc656f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -48,26 +48,7 @@ check-changelog: after_script: '' cache: {} script: - - echo $CI_MERGE_REQUEST_IID - - > - ls changelog.d - count=0 - for i in add remove fix security skip; do - [ -f changelog.d/"$CI_MERGE_REQUEST_IID"."$i" ] - retcode=$? - if [ $retcode -eq 0 ]; then - echo "found $CI_MERGE_REQUEST_IID.$i" - else - echo "no $CI_MERGE_REQUEST_IID.$i" - fi - count=$(( $count + 1 - $retcode )) - done - if [ $count -eq 1 ]; then - echo "ok" - else - echo "must have a changelog entry or explicitly skip it" - exit 1 - fi + - sh ./tools/check-changelog build: stage: build diff --git a/tools/check-changelog b/tools/check-changelog new file mode 100644 index 000000000..970f3066f --- /dev/null +++ b/tools/check-changelog @@ -0,0 +1,22 @@ +#!/bin/sh + +echo $CI_MERGE_REQUEST_IID +ls changelog.d +count=0 +for i in add remove fix security skip; do + [ -f changelog.d/"$CI_MERGE_REQUEST_IID"."$i" ] + retcode=$? + if [ $retcode -eq 0 ]; then + echo "found $CI_MERGE_REQUEST_IID.$i" + else + echo "no $CI_MERGE_REQUEST_IID.$i" + fi + count=$(( $count + 1 - $retcode )) +done +if [ $count -eq 1 ]; then + echo "ok" + exit 0 +else + echo "must have a changelog entry or explicitly skip it" + exit 1 +fi From e154ebbf7933123e91d5b5c6f5070e78eb3e383b Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 15 Aug 2021 21:53:04 +0300 Subject: [PATCH 0034/1008] Initial meilisearch implementation, doesn't delete posts yet --- config/config.exs | 7 ++- config/test.exs | 2 + lib/mix/tasks/pleroma/search/meilisearch.ex | 38 ++++++++++++ lib/pleroma/activity.ex | 1 + lib/pleroma/activity/search.ex | 4 +- lib/pleroma/application.ex | 6 +- lib/pleroma/search/meilisearch.ex | 60 +++++++++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 6 ++ .../controllers/search_controller.ex | 5 +- 9 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 lib/mix/tasks/pleroma/search/meilisearch.ex create mode 100644 lib/pleroma/search/meilisearch.ex diff --git a/config/config.exs b/config/config.exs index 4e21ce457..1df7dd44b 100644 --- a/config/config.exs +++ b/config/config.exs @@ -866,9 +866,14 @@ config :pleroma, Pleroma.User.Backup, config :pleroma, ConcurrentLimiter, [ {Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]}, - {Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]} + {Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}, + {Pleroma.Search, [max_running: 20, max_waiting: 50]} ] +config :pleroma, Pleroma.Search, module: Pleroma.Activity.Search + +config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/" + # 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/test.exs b/config/test.exs index d5c25f65e..d1c356f14 100644 --- a/config/test.exs +++ b/config/test.exs @@ -133,6 +133,8 @@ config :pleroma, :side_effects, ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock, logger: Pleroma.LoggerMock +config :pleroma, Pleroma.Search, module: Pleroma.Activity.Search + # Reduce recompilation time # https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects config :phoenix, :plug_init_mode, :runtime diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex new file mode 100644 index 000000000..2af8e5853 --- /dev/null +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -0,0 +1,38 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.Search.Meilisearch do + import Mix.Pleroma + + import Ecto.Query + + def run(["index"]) do + start_pleroma() + + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + Pleroma.Repo.chunk_stream( + from(Pleroma.Object, + limit: 200, + where: fragment("data->>'type' = 'Note'") and fragment("LENGTH(data->>'source') > 0") + ), + 100, + :batches + ) + |> Stream.map(fn objects -> + Enum.map(objects, fn object -> + data = object.data + %{id: object.id, source: data["source"], ap: data["id"]} + end) + end) + |> Stream.each(fn activities -> + {:ok, _} = + Pleroma.HTTP.post( + "#{endpoint}/indexes/objects/documents", + Jason.encode!(activities) + ) + end) + |> Stream.run() + end +end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index ebfd4ed45..9563136f9 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -367,6 +367,7 @@ defmodule Pleroma.Activity do end defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search + def add_to_index(_activity), do: nil def direct_conversation_id(activity, for_user) do alias Pleroma.Conversation.Participation diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index 0b9b24aa4..3dce9d355 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -136,7 +136,7 @@ defmodule Pleroma.Activity.Search do ) end - defp maybe_restrict_local(q, user) do + def maybe_restrict_local(q, user) do limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated) case {limit, user} do @@ -149,7 +149,7 @@ defmodule Pleroma.Activity.Search do defp restrict_local(q), do: where(q, local: true) - defp maybe_fetch(activities, user, search_query) do + def maybe_fetch(activities, user, search_query) do with true <- Regex.match?(~r/https?:/, search_query), {:ok, object} <- Fetcher.fetch_object_from_id(search_query), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 1c1db8c10..62d1b8b39 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -321,7 +321,11 @@ defmodule Pleroma.Application do def limiters_setup do config = Config.get(ConcurrentLimiter, []) - [Pleroma.Web.RichMedia.Helpers, Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy] + [ + Pleroma.Web.RichMedia.Helpers, + Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, + Pleroma.Search + ] |> Enum.each(fn module -> mod_config = Keyword.get(config, module, []) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex new file mode 100644 index 000000000..92e0d3429 --- /dev/null +++ b/lib/pleroma/search/meilisearch.ex @@ -0,0 +1,60 @@ +defmodule Pleroma.Search.Meilisearch do + require Logger + + alias Pleroma.Activity + + import Pleroma.Activity.Search + import Ecto.Query + + def search(user, query, options \\ []) do + limit = Enum.min([Keyword.get(options, :limit), 40]) + offset = Keyword.get(options, :offset, 0) + author = Keyword.get(options, :author) + + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, result} = + Pleroma.HTTP.post( + "#{endpoint}/indexes/objects/search", + Jason.encode!(%{q: query, offset: offset, limit: limit}) + ) + + hits = Jason.decode!(result.body)["hits"] |> Enum.map(& &1["ap"]) + + try do + hits + |> Activity.create_by_object_ap_id() + |> Activity.with_preloaded_object() + |> Activity.with_preloaded_object() + |> Activity.restrict_deactivated_users() + |> maybe_restrict_local(user) + |> maybe_restrict_author(author) + |> maybe_restrict_blocked(user) + |> maybe_fetch(user, query) + |> order_by([activity], desc: activity.id) + |> Pleroma.Repo.all() + rescue + _ -> maybe_fetch([], user, query) + end + end + + def add_to_index(activity) do + object = activity.object + + if activity.data["type"] == "Create" and not is_nil(object) and object.data["type"] == "Note" do + data = object.data + + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, result} = + Pleroma.HTTP.post( + "#{endpoint}/indexes/objects/documents", + Jason.encode!([%{id: object.id, source: data["source"], ap: data["id"]}]) + ) + + if not Map.has_key?(Jason.decode!(result.body), "updateId") do + Logger.error("Failed to add activity #{activity.id} to index: #{result.body}") + end + end + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a5d7036d9..034c3b185 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -140,6 +140,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) end) + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + ConcurrentLimiter.limit(Pleroma.Search, fn -> + Task.start(fn -> search_module.add_to_index(activity) end) + end) + {:ok, activity} else %Activity{} = activity -> diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 5e6e04734..99c33eba6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do use Pleroma.Web, :controller - alias Pleroma.Activity alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ControllerHelper @@ -100,7 +99,9 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do end defp resource_search(_, "statuses", query, options) do - statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) + search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity) + + statuses = with_fallback(fn -> search_module.search(options[:for_user], query, options) end) StatusView.render("index.json", activities: statuses, From 0318e9a59945d7a5625111157867f0f9ebaffd91 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 16 Aug 2021 10:18:01 +0300 Subject: [PATCH 0035/1008] Add logging to milisiearch index and make it use desc(id) --- lib/mix/tasks/pleroma/search/meilisearch.ex | 26 +++++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 2af8e5853..1fece96e5 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -3,8 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Search.Meilisearch do - import Mix.Pleroma + require Logger + import Mix.Pleroma import Ecto.Query def run(["index"]) do @@ -12,12 +13,25 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + {:ok, _} = + Pleroma.HTTP.post( + "#{endpoint}/indexes/objects/settings/ranking-rules", + Jason.encode!([ + "desc(id)", + "typo", + "words", + "proximity", + "attribute", + "wordsPosition", + "exactness" + ]) + ) + Pleroma.Repo.chunk_stream( from(Pleroma.Object, - limit: 200, where: fragment("data->>'type' = 'Note'") and fragment("LENGTH(data->>'source') > 0") ), - 100, + 200, :batches ) |> Stream.map(fn objects -> @@ -26,12 +40,14 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do %{id: object.id, source: data["source"], ap: data["id"]} end) end) - |> Stream.each(fn activities -> + |> Stream.each(fn objects -> {:ok, _} = Pleroma.HTTP.post( "#{endpoint}/indexes/objects/documents", - Jason.encode!(activities) + Jason.encode!(objects) ) + + IO.puts("Indexed #{Enum.count(objects)} entries") end) |> Stream.run() end From 365024abec905e427babb5403f0fccbde65f4bcd Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 16 Aug 2021 22:24:31 +0300 Subject: [PATCH 0036/1008] Ensure only indexing public posts and implement clearing and delete --- lib/mix/tasks/pleroma/search/meilisearch.ex | 15 ++++++++++++++- lib/pleroma/activity.ex | 1 + lib/pleroma/search/meilisearch.ex | 17 ++++++++++++++++- lib/pleroma/web/common_api.ex | 7 +++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 1fece96e5..0b86fdece 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -4,6 +4,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do require Logger + require Pleroma.Constants import Mix.Pleroma import Ecto.Query @@ -29,7 +30,11 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do Pleroma.Repo.chunk_stream( from(Pleroma.Object, - where: fragment("data->>'type' = 'Note'") and fragment("LENGTH(data->>'source') > 0") + # Only index public posts which are notes and have some text + where: + fragment("data->>'type' = 'Note'") and + fragment("LENGTH(data->>'source') > 0") and + fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()) ), 200, :batches @@ -51,4 +56,12 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do end) |> Stream.run() end + + def run(["clear"]) do + start_pleroma() + + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, _} = Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects/documents", "", [], []) + end end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 9563136f9..2c168fd41 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -368,6 +368,7 @@ defmodule Pleroma.Activity do defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search def add_to_index(_activity), do: nil + def remove_from_index(_object), do: nil def direct_conversation_id(activity, for_user) do alias Pleroma.Conversation.Participation diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 92e0d3429..dbe6b2d67 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -1,5 +1,6 @@ defmodule Pleroma.Search.Meilisearch do require Logger + require Pleroma.Constants alias Pleroma.Activity @@ -41,7 +42,8 @@ defmodule Pleroma.Search.Meilisearch do def add_to_index(activity) do object = activity.object - if activity.data["type"] == "Create" and not is_nil(object) and object.data["type"] == "Note" do + if activity.data["type"] == "Create" and not is_nil(object) and object.data["type"] == "Note" and + Pleroma.Constants.as_public() in object.data["to"] do data = object.data endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) @@ -57,4 +59,17 @@ defmodule Pleroma.Search.Meilisearch do end end end + + def remove_from_index(object) do + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, _} = + Pleroma.HTTP.request( + :delete, + "#{endpoint}/indexes/objects/documents/#{object.id}", + "", + [], + [] + ) + end end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 89f5dd606..54a8aa213 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -147,6 +147,13 @@ defmodule Pleroma.Web.CommonAPI do true <- User.superuser?(user) || user.ap_id == object.data["actor"], {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do + # Also delete from search index + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + ConcurrentLimiter.limit(Pleroma.Search, fn -> + Task.start(fn -> search_module.remove_from_index(object) end) + end) + {:ok, delete} else {:find_activity, _} -> From ea6a6a128712e81c4f298b2bb2cedfadf2295cff Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 16 Aug 2021 22:30:56 +0300 Subject: [PATCH 0037/1008] Make the indexing batch differently and more, show number indexed --- lib/mix/tasks/pleroma/search/meilisearch.ex | 65 ++++++++++++--------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 0b86fdece..2a6438528 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -28,33 +28,46 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do ]) ) - Pleroma.Repo.chunk_stream( - from(Pleroma.Object, - # Only index public posts which are notes and have some text - where: - fragment("data->>'type' = 'Note'") and - fragment("LENGTH(data->>'source') > 0") and - fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()) - ), - 200, - :batches - ) - |> Stream.map(fn objects -> - Enum.map(objects, fn object -> - data = object.data - %{id: object.id, source: data["source"], ap: data["id"]} - end) - end) - |> Stream.each(fn objects -> - {:ok, _} = - Pleroma.HTTP.post( - "#{endpoint}/indexes/objects/documents", - Jason.encode!(objects) - ) + chunk_size = 100_000 - IO.puts("Indexed #{Enum.count(objects)} entries") - end) - |> Stream.run() + Pleroma.Repo.transaction( + fn -> + Pleroma.Repo.stream( + from(Pleroma.Object, + # Only index public posts which are notes and have some text + where: + fragment("data->>'type' = 'Note'") and + fragment("LENGTH(data->>'source') > 0") and + fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()), + order_by: fragment("data->'published' DESC") + ), + timeout: :infinity + ) + |> Stream.chunk_every(chunk_size) + |> Stream.transform(0, fn objects, acc -> + new_acc = acc + Enum.count(objects) + + IO.puts("Indexed #{new_acc} entries") + + {[objects], new_acc} + end) + |> Stream.map(fn objects -> + Enum.map(objects, fn object -> + data = object.data + %{id: object.id, source: data["source"], ap: data["id"]} + end) + end) + |> Stream.each(fn objects -> + {:ok, _} = + Pleroma.HTTP.post( + "#{endpoint}/indexes/objects/documents", + Jason.encode!(objects) + ) + end) + |> Stream.run() + end, + timeout: :infinity + ) end def run(["clear"]) do From 38996f551a4ec014e9f4cb4a691d31beecab43ba Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 17 Aug 2021 00:06:32 +0300 Subject: [PATCH 0038/1008] Make meilisearch sort on publish date converted to unix time --- lib/mix/tasks/pleroma/search/meilisearch.ex | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 2a6438528..2dd9c0a62 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -18,7 +18,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do Pleroma.HTTP.post( "#{endpoint}/indexes/objects/settings/ranking-rules", Jason.encode!([ - "desc(id)", + "desc(published)", "typo", "words", "proximity", @@ -54,7 +54,15 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do |> Stream.map(fn objects -> Enum.map(objects, fn object -> data = object.data - %{id: object.id, source: data["source"], ap: data["id"]} + + {:ok, published, _} = DateTime.from_iso8601(data["published"]) + + %{ + id: object.id, + source: data["source"], + ap: data["id"], + published: published |> DateTime.to_unix() + } end) end) |> Stream.each(fn objects -> From 9beaebd97e1746df010aecfcc01d9e2e9a4c60ac Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 17 Aug 2021 00:30:14 +0300 Subject: [PATCH 0039/1008] Tweak search ordering to hopefully return newer results --- lib/mix/tasks/pleroma/search/meilisearch.ex | 15 ++++++++++++--- lib/pleroma/search/meilisearch.ex | 13 +++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 2dd9c0a62..dcecbd7cf 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -39,7 +39,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do fragment("data->>'type' = 'Note'") and fragment("LENGTH(data->>'source') > 0") and fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()), - order_by: fragment("data->'published' DESC") + order_by: [desc: fragment("data->'published'")] ), timeout: :infinity ) @@ -66,11 +66,15 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do end) end) |> Stream.each(fn objects -> - {:ok, _} = + {:ok, result} = Pleroma.HTTP.post( "#{endpoint}/indexes/objects/documents", Jason.encode!(objects) ) + + if not Map.has_key?(Jason.decode!(result.body), "updateId") do + IO.puts("Failed to index: #{result}") + end end) |> Stream.run() end, @@ -83,6 +87,11 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - {:ok, _} = Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects/documents", "", [], []) + {:ok, result} = + Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects/documents", "", [], []) + + if not Map.has_key?(Jason.decode!(result.body), "updateId") do + IO.puts("Failed to clear: #{result}") + end end end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index dbe6b2d67..9fdb0a07f 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -32,7 +32,7 @@ defmodule Pleroma.Search.Meilisearch do |> maybe_restrict_author(author) |> maybe_restrict_blocked(user) |> maybe_fetch(user, query) - |> order_by([activity], desc: activity.id) + |> order_by([object: obj], desc: obj.data["published"]) |> Pleroma.Repo.all() rescue _ -> maybe_fetch([], user, query) @@ -48,10 +48,19 @@ defmodule Pleroma.Search.Meilisearch do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + {:ok, published, _} = DateTime.from_iso8601(data["published"]) + {:ok, result} = Pleroma.HTTP.post( "#{endpoint}/indexes/objects/documents", - Jason.encode!([%{id: object.id, source: data["source"], ap: data["id"]}]) + Jason.encode!([ + %{ + id: object.id, + source: data["source"], + ap: data["id"], + published: published |> DateTime.to_unix() + } + ]) ) if not Map.has_key?(Jason.decode!(result.body), "updateId") do From 00c48a33acf0bd59fa7e7b58a67b049e4f4adc31 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 17 Aug 2021 00:57:53 +0300 Subject: [PATCH 0040/1008] Use content instead of source and scrub it --- lib/mix/tasks/pleroma/search/meilisearch.ex | 12 ++++-------- lib/pleroma/search/meilisearch.ex | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index dcecbd7cf..5270de255 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -37,7 +37,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do # Only index public posts which are notes and have some text where: fragment("data->>'type' = 'Note'") and - fragment("LENGTH(data->>'source') > 0") and + fragment("LENGTH(data->>'content') > 0") and fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()), order_by: [desc: fragment("data->'published'")] ), @@ -56,10 +56,11 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do data = object.data {:ok, published, _} = DateTime.from_iso8601(data["published"]) + {:ok, content} = FastSanitize.strip_tags(data["content"]) %{ id: object.id, - source: data["source"], + content: content, ap: data["id"], published: published |> DateTime.to_unix() } @@ -87,11 +88,6 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - {:ok, result} = - Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects/documents", "", [], []) - - if not Map.has_key?(Jason.decode!(result.body), "updateId") do - IO.puts("Failed to clear: #{result}") - end + {:ok, _} = Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects", "", [], []) end end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 9fdb0a07f..87fdeaf5e 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -56,7 +56,7 @@ defmodule Pleroma.Search.Meilisearch do Jason.encode!([ %{ id: object.id, - source: data["source"], + content: data["content"] |> Pleroma.HTML.filter_tags(), ap: data["id"], published: published |> DateTime.to_unix() } From e35d87ea54f70a39206f6103ef0e7334e2a428cc Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 17 Aug 2021 01:37:43 +0300 Subject: [PATCH 0041/1008] Make the chunk size smaller --- lib/mix/tasks/pleroma/search/meilisearch.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 5270de255..44af25f3e 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -28,7 +28,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do ]) ) - chunk_size = 100_000 + chunk_size = 10_000 Pleroma.Repo.transaction( fn -> From 2b2e409ad72862967cabf06344874ae9bff9860f Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 22 Aug 2021 16:37:52 +0300 Subject: [PATCH 0042/1008] Also index incoming federated posts --- lib/pleroma/search/search.ex | 18 ++++++++++++++++++ lib/pleroma/web/activity_pub/activity_pub.ex | 7 ++----- lib/pleroma/web/activity_pub/side_effects.ex | 7 +++++++ lib/pleroma/web/common_api.ex | 8 ++------ 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 lib/pleroma/search/search.ex diff --git a/lib/pleroma/search/search.ex b/lib/pleroma/search/search.ex new file mode 100644 index 000000000..e363abf19 --- /dev/null +++ b/lib/pleroma/search/search.ex @@ -0,0 +1,18 @@ +defmodule Pleroma.Search do + def add_to_index(activity) do + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + ConcurrentLimiter.limit(Pleroma.Search, fn -> + Task.start(fn -> search_module.add_to_index(activity) end) + end) + end + + def remove_from_index(object) do + # Also delete from search index + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + ConcurrentLimiter.limit(Pleroma.Search, fn -> + Task.start(fn -> search_module.remove_from_index(object) end) + end) + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 034c3b185..7178cf9eb 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -140,11 +140,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) end) - search_module = Pleroma.Config.get([Pleroma.Search, :module]) - - ConcurrentLimiter.limit(Pleroma.Search, fn -> - Task.start(fn -> search_module.add_to_index(activity) end) - end) + # Add local posts to search index + Pleroma.Search.add_to_index(activity) {:ok, activity} else diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 5eefd2824..15e006b18 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -197,6 +197,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do # - Increase replies count # - Set up ActivityExpiration # - Set up notifications + # - Index incoming posts for search (if needed) @impl true def handle(%{data: %{"type" => "Create"}} = activity, meta) do with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta), @@ -226,6 +227,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) end) + Pleroma.Search.add_to_index(Map.put(activity, :object, object)) + meta = meta |> add_notifications(notifications) @@ -286,6 +289,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do # - Reduce the user note count # - Reduce the reply count # - Stream out the activity + # - Removes posts from search index (if needed) @impl true def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do deleted_object = @@ -325,6 +329,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do if result == :ok do Notification.create_notifications(object) + + Pleroma.Search.remove_from_index(object) + {:ok, object, meta} else {:error, result} diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 54a8aa213..ba6c07975 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -147,12 +147,8 @@ defmodule Pleroma.Web.CommonAPI do true <- User.superuser?(user) || user.ap_id == object.data["actor"], {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do - # Also delete from search index - search_module = Pleroma.Config.get([Pleroma.Search, :module]) - - ConcurrentLimiter.limit(Pleroma.Search, fn -> - Task.start(fn -> search_module.remove_from_index(object) end) - end) + # Remove from search index for local posts + Pleroma.Search.remove_from_index(object) {:ok, delete} else From 9f16ca80e0fe60b8b0e3e8ddb9b06ca0bec31002 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 22 Aug 2021 18:47:41 +0300 Subject: [PATCH 0043/1008] Mark only content as searchable for meilisearch --- lib/mix/tasks/pleroma/search/meilisearch.ex | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 44af25f3e..ebd3cc81f 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -28,6 +28,14 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do ]) ) + {:ok, _} = + Pleroma.HTTP.post( + "#{endpoint}/indexes/objects/settings/searchable-attributes", + Jason.encode!([ + "content" + ]) + ) + chunk_size = 10_000 Pleroma.Repo.transaction( @@ -55,8 +63,14 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do Enum.map(objects, fn object -> data = object.data + content_str = + case data["content"] do + [nil | rest] -> to_string(rest) + str -> str + end + {:ok, published, _} = DateTime.from_iso8601(data["published"]) - {:ok, content} = FastSanitize.strip_tags(data["content"]) + {:ok, content} = FastSanitize.strip_tags(content_str) %{ id: object.id, From 3dedadf192a3acd0c1dfc2b11eba5a247ae7f61c Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 22 Aug 2021 19:38:03 +0300 Subject: [PATCH 0044/1008] Adjust content indexing to skip more unneeded stuff --- lib/mix/tasks/pleroma/search/meilisearch.ex | 45 ++++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index ebd3cc81f..3704e0bdc 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -52,13 +52,6 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do timeout: :infinity ) |> Stream.chunk_every(chunk_size) - |> Stream.transform(0, fn objects, acc -> - new_acc = acc + Enum.count(objects) - - IO.puts("Indexed #{new_acc} entries") - - {[objects], new_acc} - end) |> Stream.map(fn objects -> Enum.map(objects, fn object -> data = object.data @@ -70,15 +63,34 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do end {:ok, published, _} = DateTime.from_iso8601(data["published"]) - {:ok, content} = FastSanitize.strip_tags(content_str) - %{ - id: object.id, - content: content, - ap: data["id"], - published: published |> DateTime.to_unix() - } + content = + with {:ok, scrubbed} <- FastSanitize.strip_tags(content_str), + trimmed <- String.trim(scrubbed) do + trimmed + end + + # Only index if there is anything in the string. If there is a single symbol, + # it's probably a dot from mastodon posts with just the picture + if String.length(content) > 1 do + %{ + id: object.id, + content: content, + ap: data["id"], + published: published |> DateTime.to_unix() + } + else + nil + end end) + |> Enum.filter(fn o -> not is_nil(o) end) + end) + |> Stream.transform(0, fn objects, acc -> + new_acc = acc + Enum.count(objects) + + IO.puts("Indexed #{new_acc} entries") + + {[objects], new_acc} end) |> Stream.each(fn objects -> {:ok, result} = @@ -102,6 +114,9 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - {:ok, _} = Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects", "", [], []) + {:ok, _} = + Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects/documents", "", [], + timeout: :infinity + ) end end From 35e9192cedcbc56fb07c9933e2988bf900256b53 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 22 Aug 2021 22:53:18 +0300 Subject: [PATCH 0045/1008] Rework task indexing to share code with the main module The code in the main module now scrubs new posts too --- lib/mix/tasks/pleroma/search/meilisearch.ex | 35 +--------------- lib/pleroma/search/meilisearch.ex | 46 ++++++++++++++------- 2 files changed, 34 insertions(+), 47 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 3704e0bdc..b5a394e34 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -51,40 +51,9 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do ), timeout: :infinity ) + |> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1) + |> Stream.filter(fn o -> not is_nil(o) end) |> Stream.chunk_every(chunk_size) - |> Stream.map(fn objects -> - Enum.map(objects, fn object -> - data = object.data - - content_str = - case data["content"] do - [nil | rest] -> to_string(rest) - str -> str - end - - {:ok, published, _} = DateTime.from_iso8601(data["published"]) - - content = - with {:ok, scrubbed} <- FastSanitize.strip_tags(content_str), - trimmed <- String.trim(scrubbed) do - trimmed - end - - # Only index if there is anything in the string. If there is a single symbol, - # it's probably a dot from mastodon posts with just the picture - if String.length(content) > 1 do - %{ - id: object.id, - content: content, - ap: data["id"], - published: published |> DateTime.to_unix() - } - else - nil - end - end) - |> Enum.filter(fn o -> not is_nil(o) end) - end) |> Stream.transform(0, fn objects, acc -> new_acc = acc + Enum.count(objects) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 87fdeaf5e..10468e36c 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -39,28 +39,46 @@ defmodule Pleroma.Search.Meilisearch do end end - def add_to_index(activity) do - object = activity.object - - if activity.data["type"] == "Create" and not is_nil(object) and object.data["type"] == "Note" and + def object_to_search_data(object) do + if not is_nil(object) and object.data["type"] == "Note" and Pleroma.Constants.as_public() in object.data["to"] do data = object.data - endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + content_str = + case data["content"] do + [nil | rest] -> to_string(rest) + str -> str + end - {:ok, published, _} = DateTime.from_iso8601(data["published"]) + content = + with {:ok, scrubbed} <- FastSanitize.strip_tags(content_str), + trimmed <- String.trim(scrubbed) do + trimmed + end + + if String.length(content) > 1 do + {:ok, published, _} = DateTime.from_iso8601(data["published"]) + + %{ + id: object.id, + content: content, + ap: data["id"], + published: published |> DateTime.to_unix() + } + end + end + end + + def add_to_index(activity) do + maybe_search_data = object_to_search_data(activity) + + if activity.data["type"] == "Create" and maybe_search_data do + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) {:ok, result} = Pleroma.HTTP.post( "#{endpoint}/indexes/objects/documents", - Jason.encode!([ - %{ - id: object.id, - content: data["content"] |> Pleroma.HTML.filter_tags(), - ap: data["id"], - published: published |> DateTime.to_unix() - } - ]) + Jason.encode!([maybe_search_data]) ) if not Map.has_key?(Jason.decode!(result.body), "updateId") do From 410c8cb765bbec1014cb2bbdbcc44d3a25f834e1 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 22 Aug 2021 23:47:43 +0300 Subject: [PATCH 0046/1008] Make indexing logs rewrite themselves --- lib/mix/tasks/pleroma/search/meilisearch.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index b5a394e34..2485a441d 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -57,7 +57,9 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do |> Stream.transform(0, fn objects, acc -> new_acc = acc + Enum.count(objects) - IO.puts("Indexed #{new_acc} entries") + # Reset to the beginning of the line and rewrite it + IO.write("\r") + IO.write("Indexed #{new_acc} entries") {[objects], new_acc} end) @@ -76,6 +78,8 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do end, timeout: :infinity ) + + IO.write("\n") end def run(["clear"]) do From 2c7d973af7797ae860829c1764ade521a17e7263 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 23 Aug 2021 19:35:21 +0300 Subject: [PATCH 0047/1008] Implement meilisearch auth --- lib/mix/tasks/pleroma/search/meilisearch.ex | 75 ++++++++++++--------- lib/pleroma/search/meilisearch.ex | 69 ++++++++++++------- 2 files changed, 88 insertions(+), 56 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 2485a441d..230be5aa1 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -9,32 +9,30 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do import Mix.Pleroma import Ecto.Query + import Pleroma.Search.Meilisearch, only: [meili_post!: 2, meili_delete!: 1] + def run(["index"]) do start_pleroma() - endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + meili_post!( + "/indexes/objects/settings/ranking-rules", + [ + "desc(published)", + "typo", + "words", + "proximity", + "attribute", + "wordsPosition", + "exactness" + ] + ) - {:ok, _} = - Pleroma.HTTP.post( - "#{endpoint}/indexes/objects/settings/ranking-rules", - Jason.encode!([ - "desc(published)", - "typo", - "words", - "proximity", - "attribute", - "wordsPosition", - "exactness" - ]) - ) - - {:ok, _} = - Pleroma.HTTP.post( - "#{endpoint}/indexes/objects/settings/searchable-attributes", - Jason.encode!([ - "content" - ]) - ) + meili_post!( + "/indexes/objects/settings/searchable-attributes", + [ + "content" + ] + ) chunk_size = 10_000 @@ -64,14 +62,14 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do {[objects], new_acc} end) |> Stream.each(fn objects -> - {:ok, result} = - Pleroma.HTTP.post( - "#{endpoint}/indexes/objects/documents", - Jason.encode!(objects) + result = + meili_post!( + "/indexes/objects/documents", + objects ) - if not Map.has_key?(Jason.decode!(result.body), "updateId") do - IO.puts("Failed to index: #{result}") + if not Map.has_key?(result, "updateId") do + IO.puts("Failed to index: #{inspect(result)}") end end) |> Stream.run() @@ -85,11 +83,26 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do def run(["clear"]) do start_pleroma() + meili_delete!("/indexes/objects/documents") + end + + def run(["show-private-key", master_key]) do + start_pleroma() + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - {:ok, _} = - Pleroma.HTTP.request(:delete, "#{endpoint}/indexes/objects/documents", "", [], - timeout: :infinity + {:ok, result} = + Pleroma.HTTP.get( + Path.join(endpoint, "/keys"), + [{"X-Meili-API-Key", master_key}] ) + + decoded = Jason.decode!(result.body) + + if decoded["private"] do + IO.puts(decoded["private"]) + else + IO.puts("Error fetching the key, check the master key is correct: #{inspect(decoded)}") + end end end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 10468e36c..8745d539d 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -7,20 +7,50 @@ defmodule Pleroma.Search.Meilisearch do import Pleroma.Activity.Search import Ecto.Query + defp meili_headers() do + private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key]) + + if is_nil(private_key), do: [], else: [{"X-Meili-API-Key", private_key}] + end + + def meili_post!(path, params) do + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, result} = + Pleroma.HTTP.post( + Path.join(endpoint, path), + Jason.encode!(params), + meili_headers() + ) + + Jason.decode!(result.body) + end + + def meili_delete!(path) do + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, _} = + Pleroma.HTTP.request( + :delete, + Path.join(endpoint, path), + "", + meili_headers(), + timeout: :infinity + ) + end + def search(user, query, options \\ []) do limit = Enum.min([Keyword.get(options, :limit), 40]) offset = Keyword.get(options, :offset, 0) author = Keyword.get(options, :author) - endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - - {:ok, result} = - Pleroma.HTTP.post( - "#{endpoint}/indexes/objects/search", - Jason.encode!(%{q: query, offset: offset, limit: limit}) + result = + meili_post!( + "/indexes/objects/search", + %{q: query, offset: offset, limit: limit} ) - hits = Jason.decode!(result.body)["hits"] |> Enum.map(& &1["ap"]) + hits = result["hits"] |> Enum.map(& &1["ap"]) try do hits @@ -73,30 +103,19 @@ defmodule Pleroma.Search.Meilisearch do maybe_search_data = object_to_search_data(activity) if activity.data["type"] == "Create" and maybe_search_data do - endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - - {:ok, result} = - Pleroma.HTTP.post( - "#{endpoint}/indexes/objects/documents", - Jason.encode!([maybe_search_data]) + result = + meili_post!( + "/indexes/objects/documents", + [maybe_search_data] ) - if not Map.has_key?(Jason.decode!(result.body), "updateId") do - Logger.error("Failed to add activity #{activity.id} to index: #{result.body}") + if not Map.has_key?(result, "updateId") do + Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") end end end def remove_from_index(object) do - endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - - {:ok, _} = - Pleroma.HTTP.request( - :delete, - "#{endpoint}/indexes/objects/documents/#{object.id}", - "", - [], - [] - ) + meili_delete!("/indexes/objects/documents/#{object.id}") end end From a67f9da5cc46b4e184aa1afe3dd1bd1df31de15b Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 23 Aug 2021 20:02:34 +0300 Subject: [PATCH 0048/1008] Add a message with a count of posts to index --- lib/mix/tasks/pleroma/search/meilisearch.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 230be5aa1..557b06182 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -38,7 +38,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do Pleroma.Repo.transaction( fn -> - Pleroma.Repo.stream( + query = from(Pleroma.Object, # Only index public posts which are notes and have some text where: @@ -46,7 +46,13 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do fragment("LENGTH(data->>'content') > 0") and fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()), order_by: [desc: fragment("data->'published'")] - ), + ) + + count = query |> Pleroma.Repo.aggregate(:count, :data) + IO.puts("Entries to index: #{count}") + + Pleroma.Repo.stream( + query, timeout: :infinity ) |> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1) From 09a1ae1b6eca4efbb935aa1c0da950009d110fb2 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 23 Aug 2021 20:21:46 +0300 Subject: [PATCH 0049/1008] Add the meilisearch.stats command --- lib/mix/tasks/pleroma/search/meilisearch.ex | 10 +++++++++- lib/pleroma/search/meilisearch.ex | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 557b06182..f2d9fe312 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -9,7 +9,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do import Mix.Pleroma import Ecto.Query - import Pleroma.Search.Meilisearch, only: [meili_post!: 2, meili_delete!: 1] + import Pleroma.Search.Meilisearch, only: [meili_post!: 2, meili_delete!: 1, meili_get!: 1] def run(["index"]) do start_pleroma() @@ -111,4 +111,12 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do IO.puts("Error fetching the key, check the master key is correct: #{inspect(decoded)}") end end + + def run(["stats"]) do + start_pleroma() + + result = meili_get!("/indexes/objects/stats") + IO.puts("Number of entries: #{result["numberOfDocuments"]}") + IO.puts("Indexing? #{result["isIndexing"]}") + end end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 8745d539d..1ad17bf9f 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -13,6 +13,18 @@ defmodule Pleroma.Search.Meilisearch do if is_nil(private_key), do: [], else: [{"X-Meili-API-Key", private_key}] end + def meili_get!(path) do + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + {:ok, result} = + Pleroma.HTTP.get( + Path.join(endpoint, path), + meili_headers() + ) + + Jason.decode!(result.body) + end + def meili_post!(path, params) do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) From 07ccab9766a6289326676a4814537564f25f35fa Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 23 Aug 2021 20:27:16 +0300 Subject: [PATCH 0050/1008] Add search/meilisearch documentation --- docs/configuration/search.md | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 docs/configuration/search.md diff --git a/docs/configuration/search.md b/docs/configuration/search.md new file mode 100644 index 000000000..14ec2bc63 --- /dev/null +++ b/docs/configuration/search.md @@ -0,0 +1,99 @@ +# Configuring search + +{! backend/administration/CLI_tasks/general_cli_task_info.include !} + +## Built-in search + +To use built-in search that has no external dependencies, set the search module to `Pleroma.Activity`: + +> config :pleroma, Pleroma.Search, module: Pleroma.Activity + +While it has no external dependencies, it has problems with performance and relevancy. + +## Meilisearch + +To use [meilisearch](https://www.meilisearch.com/), set the search module to `Pleroma.Search.Meilisearch`: + +> config :pleroma, Pleroma.Search, module: Pleroma.Search.Meilisearch + +You then need to set the address of the meilisearch instance, and optionally the private key for authentication. + +> config :pleroma, Pleroma.Search.Meilisearch, +> url: "http://127.0.0.1:7700/", +> private_key: "private key" + +Information about setting up meilisearch can be found in the +[official documentation](https://docs.meilisearch.com/learn/getting_started/installation.html). +You probably want to start it with `MEILI_NO_ANALYTICS=true` and `MEILI_NO_CENTRY=true` environment variables, +to disable analytics. + +### Private key authentication (optional) + +To set the private key, use the `MEILI_MASTER_KEY` environment variable when starting. After setting the _master key_, +you have to get the _private key_, which is actually used for authentication. + +=== "OTP" + ```sh + ./bin/pleroma_ctl search.meilisearch show-private-key + ``` + +=== "From Source" + ```sh + mix pleroma.search.meilisearch show-private-key + ``` + +This is the key you actually put into your configuration file. + +### Initial indexing + +After setting up the configuration, you'll want to index all of your already existsing posts. Only public posts are indexed. You'll only +have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. This is also a fairly RAM +consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2 +million posts while idle and up to 7G while indexing initially, but your experience may be different). + +To start te initial indexing, run the `index` command: + +=== "OTP" + ```sh + ./bin/pleroma_ctl search.meilisearch index + ``` + +=== "From Source" + ```sh + mix pleroma.search.meilisearch index + ``` + +This will show you the total amount of posts to index, and then show you the amount of posts indexed currently, until the numbers eventually +become the same. The posts are indexed in big batches and meilisearch will take some time to actually index them, even after you have +inserted all the posts into it. Depending on the amount of posts, this may be as long as several hours. To get information about the status +of indexing and how many posts have actually been indexed, use the `stats` command: + +=== "OTP" + ```sh + ./bin/pleroma_ctl search.meilisearch stats + ``` + +=== "From Source" + ```sh + mix pleroma.search.meilisearch stats + ``` + +### Clearing the index + +In case you need to clear the index (for example, to re-index from scratch, if that needs to happen for some reason), you can +use the `clear` command: + +=== "OTP" + ```sh + ./bin/pleroma_ctl search.meilisearch clear + ``` + +=== "From Source" + ```sh + mix pleroma.search.meilisearch clear + ``` + +This will clear **all** the posts from the search index. Note, that deleted posts are also removed from index by the instance itself, so +there is no need to actually clear the whole index, unless you want **all** of it gone. That said, the index does not hold any information +that cannot be re-created from the database, it should also generally be a lot smaller than the size of your database. Still, the size +depends on the amount of text in posts. From d9ef7e075880ba39dd4ca8e21566c680070faa42 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 23 Aug 2021 21:15:15 +0300 Subject: [PATCH 0051/1008] Fix activity being passed to objec_to_search_data --- lib/pleroma/search/meilisearch.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 1ad17bf9f..212bdd473 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -112,7 +112,7 @@ defmodule Pleroma.Search.Meilisearch do end def add_to_index(activity) do - maybe_search_data = object_to_search_data(activity) + maybe_search_data = object_to_search_data(activity.object) if activity.data["type"] == "Create" and maybe_search_data do result = From 005947e9f77b40d1b6dd6c05f952df6ecb2aa1fc Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 23 Aug 2021 23:52:21 +0300 Subject: [PATCH 0052/1008] Add tests for local post indexing for meilisearch --- config/test.exs | 4 +- test/pleroma/search/meilisearch_test.exs | 108 +++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 test/pleroma/search/meilisearch_test.exs diff --git a/config/test.exs b/config/test.exs index d1c356f14..c9b2b51ba 100644 --- a/config/test.exs +++ b/config/test.exs @@ -133,7 +133,9 @@ config :pleroma, :side_effects, ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock, logger: Pleroma.LoggerMock -config :pleroma, Pleroma.Search, module: Pleroma.Activity.Search +config :pleroma, Pleroma.Search, module: Pleroma.Activity + +config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/", private_key: nil # Reduce recompilation time # https://dashbit.co/blog/speeding-up-re-compilation-of-elixir-projects diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs new file mode 100644 index 000000000..6e13c8edf --- /dev/null +++ b/test/pleroma/search/meilisearch_test.exs @@ -0,0 +1,108 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Search.MeilisearchTest do + require Pleroma.Constants + + use Pleroma.DataCase + + import Pleroma.Factory + import Tesla.Mock + import Mock + + alias Pleroma.Web.CommonAPI + alias Pleroma.Search.Meilisearch + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + describe "meilisearch" do + setup do: clear_config([Pleroma.Search, :module], Meilisearch) + + setup_with_mocks( + [ + {Meilisearch, [:passthrough], + [ + add_to_index: fn a -> passthrough([a]) end, + remove_from_index: fn a -> passthrough([a]) end + ]} + ], + context, + do: {:ok, context} + ) + + test "indexes a local post on creation" do + user = insert(:user) + + mock_global(fn + %{method: :post, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> + assert match?( + [%{"content" => "guys i just don't wanna leave the swamp"}], + Jason.decode!(body) + ) + + json(%{updateId: 1}) + end) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "guys i just don't wanna leave the swamp", + visibility: "public" + }) + + assert_called(Meilisearch.add_to_index(activity)) + end + + test "doesn't index posts that are not public" do + user = insert(:user) + + Enum.each(["unlisted", "private", "direct"], fn visiblity -> + {:ok, _} = + CommonAPI.post(user, %{ + status: "guys i just don't wanna leave the swamp", + visibility: visiblity + }) + end) + + history = call_history(Meilisearch) + assert Enum.count(history) == 3 + + Enum.each(history, fn {_, _, return} -> + assert is_nil(return) + end) + end + + test "deletes posts from index when deleted locally" do + user = insert(:user) + + mock_global(fn + %{method: :post, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> + assert match?( + [%{"content" => "guys i just don't wanna leave the swamp"}], + Jason.decode!(body) + ) + + json(%{updateId: 1}) + + %{method: :delete, url: "http://127.0.0.1:7700/indexes/objects/documents/" <> id} -> + assert String.length(id) > 1 + json(%{updateId: 2}) + end) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "guys i just don't wanna leave the swamp", + visibility: "public" + }) + + assert_called(Meilisearch.add_to_index(activity)) + + {:ok, _} = CommonAPI.delete(activity.id, user) + + assert_called(Meilisearch.remove_from_index(:_)) + end + end +end From a5bb7f9345ff73469c0d776bce5455ec4f27b4ee Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 23 Aug 2021 23:52:37 +0300 Subject: [PATCH 0053/1008] Add private_key: nil to default meilisearch options --- config/config.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 1df7dd44b..711775982 100644 --- a/config/config.exs +++ b/config/config.exs @@ -872,7 +872,7 @@ config :pleroma, ConcurrentLimiter, [ config :pleroma, Pleroma.Search, module: Pleroma.Activity.Search -config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/" +config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/", private_key: nil # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. From 40280cc273ad7f2b355846e2f41b9873a8d5ff2c Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 28 Aug 2021 15:59:13 +0300 Subject: [PATCH 0054/1008] Reorder ranking rules for (maybe) better results --- lib/mix/tasks/pleroma/search/meilisearch.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index f2d9fe312..cdf9ab0bd 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -18,12 +18,12 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do "/indexes/objects/settings/ranking-rules", [ "desc(published)", - "typo", "words", + "exactness", "proximity", - "attribute", "wordsPosition", - "exactness" + "typo", + "attribute" ] ) From 6beef2d1179ab9a377e87872b7fbe2997bbbbebd Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Fri, 8 Oct 2021 12:24:37 +0300 Subject: [PATCH 0055/1008] Move add_to_index / remove_from_index to Pleroma.Actitivy.Search --- lib/pleroma/activity.ex | 2 -- lib/pleroma/activity/search.ex | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 2c168fd41..ebfd4ed45 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -367,8 +367,6 @@ defmodule Pleroma.Activity do end defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search - def add_to_index(_activity), do: nil - def remove_from_index(_object), do: nil def direct_conversation_id(activity, for_user) do alias Pleroma.Conversation.Participation diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index 3dce9d355..47ab5208c 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -45,6 +45,9 @@ defmodule Pleroma.Activity.Search do end end + def add_to_index(_activity), do: nil + def remove_from_index(_object), do: nil + def maybe_restrict_author(query, %User{} = author) do Activity.Queries.by_author(query, author) end From c569ad05b3d812c87171e68eac79eec749321033 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 12 Oct 2021 19:14:39 +0300 Subject: [PATCH 0056/1008] Add more documentation about rum to meilisearch docs --- docs/configuration/search.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/configuration/search.md b/docs/configuration/search.md index 14ec2bc63..e9743f1a4 100644 --- a/docs/configuration/search.md +++ b/docs/configuration/search.md @@ -12,6 +12,15 @@ While it has no external dependencies, it has problems with performance and rele ## Meilisearch +Note that it's quite a bit more memory hungry than PostgreSQL (around 4-5G for ~1.2 million +posts while idle and up to 7G while indexing initially). The disk usage for this additional index is also +around 4 gigabytes. Like [RUM](./cheatsheet.md#rum-indexing-for-full-text-search) indexes, it offers considerably +higher performance and ordering by timestamp in a reasonable amount of time. +Additionally, the search results seem to be more accurate. + +Due to high memory usage, it may be best to set it up on a different machine, if running pleroma on a low-resource +computer, and use private key authentication to secure the remote search instance. + To use [meilisearch](https://www.meilisearch.com/), set the search module to `Pleroma.Search.Meilisearch`: > config :pleroma, Pleroma.Search, module: Pleroma.Search.Meilisearch From 95cb2bb694e3f8857895b21331b02b9277d65d9b Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 12 Oct 2021 19:17:37 +0300 Subject: [PATCH 0057/1008] Don't try removing from index again in common_api It's already removed in the side effects of the pipeline --- lib/pleroma/web/common_api.ex | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index ba6c07975..89f5dd606 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -147,9 +147,6 @@ defmodule Pleroma.Web.CommonAPI do true <- User.superuser?(user) || user.ap_id == object.data["actor"], {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do - # Remove from search index for local posts - Pleroma.Search.remove_from_index(object) - {:ok, delete} else {:find_activity, _} -> From cf558208c202d5188954e26077d35bcc1ae02fce Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 12 Oct 2021 19:34:57 +0300 Subject: [PATCH 0058/1008] Use proper deleted object for removing from index --- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 15e006b18..4762b5ac6 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -330,7 +330,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do if result == :ok do Notification.create_notifications(object) - Pleroma.Search.remove_from_index(object) + Pleroma.Search.remove_from_index(deleted_object) {:ok, object, meta} else From e4b7a3f51f270f468c15cc4ce850c847633c030b Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Fri, 29 Oct 2021 00:38:00 +0300 Subject: [PATCH 0059/1008] Modify some meilisearch variables --- config/config.exs | 2 +- lib/pleroma/search/meilisearch.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index 711775982..b55dd94cb 100644 --- a/config/config.exs +++ b/config/config.exs @@ -867,7 +867,7 @@ config :pleroma, Pleroma.User.Backup, config :pleroma, ConcurrentLimiter, [ {Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]}, {Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy, [max_running: 5, max_waiting: 5]}, - {Pleroma.Search, [max_running: 20, max_waiting: 50]} + {Pleroma.Search, [max_running: 30, max_waiting: 50]} ] config :pleroma, Pleroma.Search, module: Pleroma.Activity.Search diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 212bdd473..b8248e40c 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -47,7 +47,7 @@ defmodule Pleroma.Search.Meilisearch do Path.join(endpoint, path), "", meili_headers(), - timeout: :infinity + [] ) end From 0b4fd0d342e3ced073e82355b380cbfee5478c60 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Fri, 29 Oct 2021 13:58:24 +0300 Subject: [PATCH 0060/1008] Set content-type to application/json --- lib/pleroma/search/meilisearch.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index b8248e40c..d94ab8b64 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -10,7 +10,8 @@ defmodule Pleroma.Search.Meilisearch do defp meili_headers() do private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key]) - if is_nil(private_key), do: [], else: [{"X-Meili-API-Key", private_key}] + [{"Content-Type", "application/json"}] ++ + if is_nil(private_key), do: [], else: [{"X-Meili-API-Key", private_key}] end def meili_get!(path) do From 4445421297f4a4375ce9df4857a66ad08e984507 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Fri, 29 Oct 2021 21:04:59 +0300 Subject: [PATCH 0061/1008] Only add local posts to index in activity_pub Remote ones are already added in another place --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 7178cf9eb..cdc70aacf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -141,7 +141,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end) # Add local posts to search index - Pleroma.Search.add_to_index(activity) + if local, do: Pleroma.Search.add_to_index(activity) {:ok, activity} else From e928e307f34542b0a0af8b615c986aeac478b637 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 10 Nov 2021 21:25:12 +0300 Subject: [PATCH 0062/1008] Add a reindex option Signed-off-by: Ekaterina Vaartis --- lib/mix/tasks/pleroma/search/meilisearch.ex | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index cdf9ab0bd..2a3c3a8b9 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -11,9 +11,11 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do import Pleroma.Search.Meilisearch, only: [meili_post!: 2, meili_delete!: 1, meili_get!: 1] - def run(["index"]) do + def run(["index" | args]) do start_pleroma() + is_reindex = "--reindex" in args + meili_post!( "/indexes/objects/settings/ranking-rules", [ @@ -68,6 +70,19 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do {[objects], new_acc} end) |> Stream.each(fn objects -> + objects = + objects + |> Enum.filter(fn o -> + if is_reindex do + result = meili_get!("/indexes/objects/documents/#{o.id}") + + # Filter out the already indexed documents. This is true when the document does not exist + result["errorCode"] == "document_not_found" + else + true + end + end) + result = meili_post!( "/indexes/objects/documents", From 9c1a9307079c8d007ae7cbf3e089d2bc5ea6b733 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 13 Nov 2021 15:07:51 +0300 Subject: [PATCH 0063/1008] Support reindexing meilisearch >=0.24.0 It has has a different error code key --- lib/mix/tasks/pleroma/search/meilisearch.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 2a3c3a8b9..3b134ad3f 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -76,8 +76,14 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do if is_reindex do result = meili_get!("/indexes/objects/documents/#{o.id}") + # With >= 0.24.0 the name for "errorCode" is just "code" + error_code_key = + if meili_get!("/version")["pkgVersion"] |> Version.match?(">= 0.24.0"), + do: "code", + else: "errorCode" + # Filter out the already indexed documents. This is true when the document does not exist - result["errorCode"] == "document_not_found" + result[error_code_key] == "document_not_found" else true end From 8898b5e927bae27a521e4eadd0faf970ad27c5bc Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 14 Nov 2021 20:15:12 +0300 Subject: [PATCH 0064/1008] Fix a typo in search docs --- docs/configuration/search.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/search.md b/docs/configuration/search.md index e9743f1a4..9adc7884f 100644 --- a/docs/configuration/search.md +++ b/docs/configuration/search.md @@ -60,7 +60,7 @@ have to do it one time, but it might take a while, depending on the amount of po consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2 million posts while idle and up to 7G while indexing initially, but your experience may be different). -To start te initial indexing, run the `index` command: +To start the initial indexing, run the `index` command: === "OTP" ```sh From 7009ef5672ad20f92374d218cd614a38cd70515e Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 14 Nov 2021 20:24:05 +0300 Subject: [PATCH 0065/1008] Move the search.ex file so credo doesn't complain --- lib/pleroma/{search => }/search.ex | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/pleroma/{search => }/search.ex (100%) diff --git a/lib/pleroma/search/search.ex b/lib/pleroma/search.ex similarity index 100% rename from lib/pleroma/search/search.ex rename to lib/pleroma/search.ex From 39e596a5b51c0c86b6d6bd5f23177a1e6a64cf0b Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sun, 14 Nov 2021 21:42:18 +0300 Subject: [PATCH 0066/1008] Style fixes --- lib/mix/tasks/pleroma/search/meilisearch.ex | 3 ++- lib/pleroma/search/meilisearch.ex | 2 +- test/pleroma/search/meilisearch_test.exs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 3b134ad3f..62ace7e39 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -82,7 +82,8 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do do: "code", else: "errorCode" - # Filter out the already indexed documents. This is true when the document does not exist + # Filter out the already indexed documents. + # This is true when the document does not exist result[error_code_key] == "document_not_found" else true diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index d94ab8b64..41f99ad9f 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Search.Meilisearch do import Pleroma.Activity.Search import Ecto.Query - defp meili_headers() do + defp meili_headers do private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key]) [{"Content-Type", "application/json"}] ++ diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index 6e13c8edf..251388ea2 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -11,8 +11,8 @@ defmodule Pleroma.Search.MeilisearchTest do import Tesla.Mock import Mock - alias Pleroma.Web.CommonAPI alias Pleroma.Search.Meilisearch + alias Pleroma.Web.CommonAPI setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) From 0fae71f88d142f64ec18a49ff4292db816dacdc8 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 16 Nov 2021 21:54:26 +0300 Subject: [PATCH 0067/1008] Rename search.ex to database_search.ex and add search/2 --- lib/pleroma/{search.ex => search/database_search.ex} | 8 +++++++- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/side_effects.ex | 4 ++-- .../web/mastodon_api/controllers/search_controller.ex | 4 +--- 4 files changed, 11 insertions(+), 7 deletions(-) rename lib/pleroma/{search.ex => search/database_search.ex} (68%) diff --git a/lib/pleroma/search.ex b/lib/pleroma/search/database_search.ex similarity index 68% rename from lib/pleroma/search.ex rename to lib/pleroma/search/database_search.ex index e363abf19..be0e19be0 100644 --- a/lib/pleroma/search.ex +++ b/lib/pleroma/search/database_search.ex @@ -1,4 +1,4 @@ -defmodule Pleroma.Search do +defmodule Pleroma.Search.DatabaseSearch do def add_to_index(activity) do search_module = Pleroma.Config.get([Pleroma.Search, :module]) @@ -15,4 +15,10 @@ defmodule Pleroma.Search do Task.start(fn -> search_module.remove_from_index(object) end) end) end + + def search(query, options) do + search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity) + + search_module.search(options[:for_user], query, options) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index cdc70aacf..7e3444676 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -141,7 +141,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end) # Add local posts to search index - if local, do: Pleroma.Search.add_to_index(activity) + if local, do: Pleroma.Search.DatabaseSearch.add_to_index(activity) {:ok, activity} else diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 4762b5ac6..fa57eab69 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -227,7 +227,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) end) - Pleroma.Search.add_to_index(Map.put(activity, :object, object)) + Pleroma.Search.DatabaseSearch.add_to_index(Map.put(activity, :object, object)) meta = meta @@ -330,7 +330,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do if result == :ok do Notification.create_notifications(object) - Pleroma.Search.remove_from_index(deleted_object) + Pleroma.Search.DatabaseSearch.remove_from_index(deleted_object) {:ok, object, meta} else diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 99c33eba6..10f1aa532 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -99,9 +99,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do end defp resource_search(_, "statuses", query, options) do - search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity) - - statuses = with_fallback(fn -> search_module.search(options[:for_user], query, options) end) + statuses = with_fallback(fn -> Pleroma.Search.DatabaseSearch.search(query, options) end) StatusView.render("index.json", activities: statuses, From a6946048fbe049aa223d094d36eb767739ab5ff2 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 17 Nov 2021 22:29:49 +0300 Subject: [PATCH 0068/1008] Rename Activity.Search to Search.DatabaseSearch --- config/config.exs | 2 +- config/test.exs | 2 +- docs/configuration/search.md | 2 +- lib/pleroma/activity.ex | 2 +- lib/pleroma/activity/search.ex | 165 ------------------ lib/pleroma/search.ex | 24 +++ lib/pleroma/search/database_search.ex | 157 +++++++++++++++-- lib/pleroma/search/meilisearch.ex | 2 +- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/side_effects.ex | 4 +- .../controllers/search_controller.ex | 2 +- .../database_search_test.ex} | 10 +- 12 files changed, 181 insertions(+), 193 deletions(-) delete mode 100644 lib/pleroma/activity/search.ex create mode 100644 lib/pleroma/search.ex rename test/pleroma/{activity/search_test.exs => search/database_search_test.ex} (86%) diff --git a/config/config.exs b/config/config.exs index b55dd94cb..f7f3a1454 100644 --- a/config/config.exs +++ b/config/config.exs @@ -870,7 +870,7 @@ config :pleroma, ConcurrentLimiter, [ {Pleroma.Search, [max_running: 30, max_waiting: 50]} ] -config :pleroma, Pleroma.Search, module: Pleroma.Activity.Search +config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/", private_key: nil diff --git a/config/test.exs b/config/test.exs index c9b2b51ba..ea29be638 100644 --- a/config/test.exs +++ b/config/test.exs @@ -133,7 +133,7 @@ config :pleroma, :side_effects, ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock, logger: Pleroma.LoggerMock -config :pleroma, Pleroma.Search, module: Pleroma.Activity +config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/", private_key: nil diff --git a/docs/configuration/search.md b/docs/configuration/search.md index 9adc7884f..c7e77d9c2 100644 --- a/docs/configuration/search.md +++ b/docs/configuration/search.md @@ -6,7 +6,7 @@ To use built-in search that has no external dependencies, set the search module to `Pleroma.Activity`: -> config :pleroma, Pleroma.Search, module: Pleroma.Activity +> config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch While it has no external dependencies, it has problems with performance and relevancy. diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index ebfd4ed45..389c80691 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -366,7 +366,7 @@ defmodule Pleroma.Activity do from(activity in query, where: activity.actor not in subquery(deactivated_users_query)) end - defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search + defdelegate search(user, query, options \\ []), to: Pleroma.Search.DatabaseSearch def direct_conversation_id(activity, for_user) do alias Pleroma.Conversation.Participation diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex deleted file mode 100644 index 47ab5208c..000000000 --- a/lib/pleroma/activity/search.ex +++ /dev/null @@ -1,165 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Activity.Search do - alias Pleroma.Activity - alias Pleroma.Object.Fetcher - alias Pleroma.Pagination - alias Pleroma.User - alias Pleroma.Web.ActivityPub.Visibility - - require Pleroma.Constants - - import Ecto.Query - - def search(user, search_query, options \\ []) do - index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin - limit = Enum.min([Keyword.get(options, :limit), 40]) - offset = Keyword.get(options, :offset, 0) - author = Keyword.get(options, :author) - - search_function = - if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do - :websearch - else - :plain - end - - try do - Activity - |> Activity.with_preloaded_object() - |> Activity.restrict_deactivated_users() - |> restrict_public(user) - |> query_with(index_type, search_query, search_function) - |> maybe_restrict_local(user) - |> maybe_restrict_author(author) - |> maybe_restrict_blocked(user) - |> Pagination.fetch_paginated( - %{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum}, - :offset - ) - |> maybe_fetch(user, search_query) - rescue - _ -> maybe_fetch([], user, search_query) - end - end - - def add_to_index(_activity), do: nil - def remove_from_index(_object), do: nil - - def maybe_restrict_author(query, %User{} = author) do - Activity.Queries.by_author(query, author) - end - - def maybe_restrict_author(query, _), do: query - - def maybe_restrict_blocked(query, %User{} = user) do - Activity.Queries.exclude_authors(query, User.blocked_users_ap_ids(user)) - end - - def maybe_restrict_blocked(query, _), do: query - - defp restrict_public(q, user) when not is_nil(user) do - intended_recipients = [ - Pleroma.Constants.as_public(), - Pleroma.Web.ActivityPub.Utils.as_local_public() - ] - - from([a, o] in q, - where: fragment("?->>'type' = 'Create'", a.data), - where: fragment("? && ?", ^intended_recipients, a.recipients) - ) - end - - defp restrict_public(q, _user) do - from([a, o] in q, - where: fragment("?->>'type' = 'Create'", a.data), - where: ^Pleroma.Constants.as_public() in a.recipients - ) - end - - defp query_with(q, :gin, search_query, :plain) do - %{rows: [[tsc]]} = - Ecto.Adapters.SQL.query!( - Pleroma.Repo, - "select current_setting('default_text_search_config')::regconfig::oid;" - ) - - from([a, o] in q, - where: - fragment( - "to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)", - ^tsc, - o.data, - ^search_query - ) - ) - end - - defp query_with(q, :gin, search_query, :websearch) do - %{rows: [[tsc]]} = - Ecto.Adapters.SQL.query!( - Pleroma.Repo, - "select current_setting('default_text_search_config')::regconfig::oid;" - ) - - from([a, o] in q, - where: - fragment( - "to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)", - ^tsc, - o.data, - ^search_query - ) - ) - end - - defp query_with(q, :rum, search_query, :plain) do - from([a, o] in q, - where: - fragment( - "? @@ plainto_tsquery(?)", - o.fts_content, - ^search_query - ), - order_by: [fragment("? <=> now()::date", o.inserted_at)] - ) - end - - defp query_with(q, :rum, search_query, :websearch) do - from([a, o] in q, - where: - fragment( - "? @@ websearch_to_tsquery(?)", - o.fts_content, - ^search_query - ), - order_by: [fragment("? <=> now()::date", o.inserted_at)] - ) - end - - def maybe_restrict_local(q, user) do - limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated) - - case {limit, user} do - {:all, _} -> restrict_local(q) - {:unauthenticated, %User{}} -> q - {:unauthenticated, _} -> restrict_local(q) - {false, _} -> q - end - end - - defp restrict_local(q), do: where(q, local: true) - - def maybe_fetch(activities, user, search_query) do - with true <- Regex.match?(~r/https?:/, search_query), - {:ok, object} <- Fetcher.fetch_object_from_id(search_query), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), - true <- Visibility.visible_for_user?(activity, user) do - [activity | activities] - else - _ -> activities - end - end -end diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex new file mode 100644 index 000000000..ae0b28c54 --- /dev/null +++ b/lib/pleroma/search.ex @@ -0,0 +1,24 @@ +defmodule Pleroma.Search do + def add_to_index(activity) do + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + ConcurrentLimiter.limit(Pleroma.Search, fn -> + Task.start(fn -> search_module.add_to_index(activity) end) + end) + end + + def remove_from_index(object) do + # Also delete from search index + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + ConcurrentLimiter.limit(Pleroma.Search, fn -> + Task.start(fn -> search_module.remove_from_index(object) end) + end) + end + + def search(query, options) do + search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity) + + search_module.search(options[:for_user], query, options) + end +end diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex index be0e19be0..5a8b8ca67 100644 --- a/lib/pleroma/search/database_search.ex +++ b/lib/pleroma/search/database_search.ex @@ -1,24 +1,153 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Search.DatabaseSearch do - def add_to_index(activity) do - search_module = Pleroma.Config.get([Pleroma.Search, :module]) + alias Pleroma.Activity + alias Pleroma.Object.Fetcher + alias Pleroma.Pagination + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Visibility - ConcurrentLimiter.limit(Pleroma.Search, fn -> - Task.start(fn -> search_module.add_to_index(activity) end) - end) + require Pleroma.Constants + + import Ecto.Query + + def search(user, search_query, options \\ []) do + index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin + limit = Enum.min([Keyword.get(options, :limit), 40]) + offset = Keyword.get(options, :offset, 0) + author = Keyword.get(options, :author) + + search_function = + if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do + :websearch + else + :plain + end + + try do + Activity + |> Activity.with_preloaded_object() + |> Activity.restrict_deactivated_users() + |> restrict_public() + |> query_with(index_type, search_query, search_function) + |> maybe_restrict_local(user) + |> maybe_restrict_author(author) + |> maybe_restrict_blocked(user) + |> Pagination.fetch_paginated( + %{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum}, + :offset + ) + |> maybe_fetch(user, search_query) + rescue + _ -> maybe_fetch([], user, search_query) + end end - def remove_from_index(object) do - # Also delete from search index - search_module = Pleroma.Config.get([Pleroma.Search, :module]) + def add_to_index(_activity), do: nil + def remove_from_index(_object), do: nil - ConcurrentLimiter.limit(Pleroma.Search, fn -> - Task.start(fn -> search_module.remove_from_index(object) end) - end) + def maybe_restrict_author(query, %User{} = author) do + Activity.Queries.by_author(query, author) end - def search(query, options) do - search_module = Pleroma.Config.get([Pleroma.Search, :module], Pleroma.Activity) + def maybe_restrict_author(query, _), do: query - search_module.search(options[:for_user], query, options) + def maybe_restrict_blocked(query, %User{} = user) do + Activity.Queries.exclude_authors(query, User.blocked_users_ap_ids(user)) + end + + def maybe_restrict_blocked(query, _), do: query + + def restrict_public(q) do + from([a, o] in q, + where: fragment("?->>'type' = 'Create'", a.data), + where: ^Pleroma.Constants.as_public() in a.recipients + ) + end + + defp query_with(q, :gin, search_query, :plain) do + %{rows: [[tsc]]} = + Ecto.Adapters.SQL.query!( + Pleroma.Repo, + "select current_setting('default_text_search_config')::regconfig::oid;" + ) + + from([a, o] in q, + where: + fragment( + "to_tsvector(?::oid::regconfig, ?->>'content') @@ plainto_tsquery(?)", + ^tsc, + o.data, + ^search_query + ) + ) + end + + defp query_with(q, :gin, search_query, :websearch) do + %{rows: [[tsc]]} = + Ecto.Adapters.SQL.query!( + Pleroma.Repo, + "select current_setting('default_text_search_config')::regconfig::oid;" + ) + + from([a, o] in q, + where: + fragment( + "to_tsvector(?::oid::regconfig, ?->>'content') @@ websearch_to_tsquery(?)", + ^tsc, + o.data, + ^search_query + ) + ) + end + + defp query_with(q, :rum, search_query, :plain) do + from([a, o] in q, + where: + fragment( + "? @@ plainto_tsquery(?)", + o.fts_content, + ^search_query + ), + order_by: [fragment("? <=> now()::date", o.inserted_at)] + ) + end + + defp query_with(q, :rum, search_query, :websearch) do + from([a, o] in q, + where: + fragment( + "? @@ websearch_to_tsquery(?)", + o.fts_content, + ^search_query + ), + order_by: [fragment("? <=> now()::date", o.inserted_at)] + ) + end + + def maybe_restrict_local(q, user) do + limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated) + + case {limit, user} do + {:all, _} -> restrict_local(q) + {:unauthenticated, %User{}} -> q + {:unauthenticated, _} -> restrict_local(q) + {false, _} -> q + end + end + + defp restrict_local(q), do: where(q, local: true) + + def maybe_fetch(activities, user, search_query) do + with true <- Regex.match?(~r/https?:/, search_query), + {:ok, object} <- Fetcher.fetch_object_from_id(search_query), + %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), + true <- Visibility.visible_for_user?(activity, user) do + [activity | activities] + else + _ -> activities + end end end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 41f99ad9f..fa9e27b03 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Search.Meilisearch do alias Pleroma.Activity - import Pleroma.Activity.Search + import Pleroma.Search.DatabaseSearch import Ecto.Query defp meili_headers do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 7e3444676..cdc70aacf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -141,7 +141,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end) # Add local posts to search index - if local, do: Pleroma.Search.DatabaseSearch.add_to_index(activity) + if local, do: Pleroma.Search.add_to_index(activity) {:ok, activity} else diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index fa57eab69..4762b5ac6 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -227,7 +227,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) end) - Pleroma.Search.DatabaseSearch.add_to_index(Map.put(activity, :object, object)) + Pleroma.Search.add_to_index(Map.put(activity, :object, object)) meta = meta @@ -330,7 +330,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do if result == :ok do Notification.create_notifications(object) - Pleroma.Search.DatabaseSearch.remove_from_index(deleted_object) + Pleroma.Search.remove_from_index(deleted_object) {:ok, object, meta} else diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 10f1aa532..e4acba226 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -99,7 +99,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do end defp resource_search(_, "statuses", query, options) do - statuses = with_fallback(fn -> Pleroma.Search.DatabaseSearch.search(query, options) end) + statuses = with_fallback(fn -> Pleroma.Search.search(query, options) end) StatusView.render("index.json", activities: statuses, diff --git a/test/pleroma/activity/search_test.exs b/test/pleroma/search/database_search_test.ex similarity index 86% rename from test/pleroma/activity/search_test.exs rename to test/pleroma/search/database_search_test.ex index 3b5fd2c3c..c123d0b84 100644 --- a/test/pleroma/activity/search_test.exs +++ b/test/pleroma/search/database_search_test.ex @@ -2,8 +2,8 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Activity.SearchTest do - alias Pleroma.Activity.Search +defmodule Pleroma.Search.DatabaseSearchTest do + alias Pleroma.Search.DatabaseSearch alias Pleroma.Web.CommonAPI import Pleroma.Factory @@ -13,7 +13,7 @@ defmodule Pleroma.Activity.SearchTest do user = insert(:user) {:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"}) - [result] = Search.search(nil, "wednesday") + [result] = DatabaseSearch.search(nil, "wednesday") assert result.id == post.id end @@ -45,7 +45,7 @@ defmodule Pleroma.Activity.SearchTest do {:ok, _post2} = CommonAPI.post(user, %{status: "it's wednesday my bros"}) # plainto doesn't understand complex queries - assert [result] = Search.search(nil, "wednesday -dudes") + assert [result] = DatabaseSearch.search(nil, "wednesday -dudes") assert result.id == post.id end @@ -55,7 +55,7 @@ defmodule Pleroma.Activity.SearchTest do {:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"}) {:ok, other_post} = CommonAPI.post(user, %{status: "it's wednesday my bros"}) - assert [result] = Search.search(nil, "wednesday -dudes") + assert [result] = DatabaseSearch.search(nil, "wednesday -dudes") assert result.id == other_post.id end From a12f63bc81481e3f852934e8cc1269e16a57cf0a Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 22 Nov 2021 21:39:54 +0300 Subject: [PATCH 0069/1008] Implement suggestions from the Meilisearch MR - Index unlisted posts - Move version check outside of the streaming and only do it once - Use a PUT request instead of checking manually if there is need to insert - Add error handling, sort of --- lib/mix/tasks/pleroma/search/meilisearch.ex | 84 +++++++++----------- lib/pleroma/search/meilisearch.ex | 85 ++++++++++++++------- 2 files changed, 93 insertions(+), 76 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 62ace7e39..6730a99a9 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -3,38 +3,40 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.Search.Meilisearch do - require Logger require Pleroma.Constants import Mix.Pleroma import Ecto.Query - import Pleroma.Search.Meilisearch, only: [meili_post!: 2, meili_delete!: 1, meili_get!: 1] + import Pleroma.Search.Meilisearch, + only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete!: 1] - def run(["index" | args]) do + def run(["index"]) do start_pleroma() - is_reindex = "--reindex" in args + {:ok, _} = + meili_post( + "/indexes/objects/settings/ranking-rules", + [ + "desc(published)", + "words", + "exactness", + "proximity", + "wordsPosition", + "typo", + "attribute" + ] + ) - meili_post!( - "/indexes/objects/settings/ranking-rules", - [ - "desc(published)", - "words", - "exactness", - "proximity", - "wordsPosition", - "typo", - "attribute" - ] - ) + {:ok, _} = + meili_post( + "/indexes/objects/settings/searchable-attributes", + [ + "content" + ] + ) - meili_post!( - "/indexes/objects/settings/searchable-attributes", - [ - "content" - ] - ) + IO.puts("Created indices. Starting to insert posts.") chunk_size = 10_000 @@ -42,11 +44,11 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do fn -> query = from(Pleroma.Object, - # Only index public posts which are notes and have some text + # Only index public and unlisted posts which are notes and have some text where: fragment("data->>'type' = 'Note'") and - fragment("LENGTH(data->>'content') > 0") and - fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()), + (fragment("data->'to' \\? ?", ^Pleroma.Constants.as_public()) or + fragment("data->'cc' \\? ?", ^Pleroma.Constants.as_public())), order_by: [desc: fragment("data->'published'")] ) @@ -70,34 +72,18 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do {[objects], new_acc} end) |> Stream.each(fn objects -> - objects = - objects - |> Enum.filter(fn o -> - if is_reindex do - result = meili_get!("/indexes/objects/documents/#{o.id}") - - # With >= 0.24.0 the name for "errorCode" is just "code" - error_code_key = - if meili_get!("/version")["pkgVersion"] |> Version.match?(">= 0.24.0"), - do: "code", - else: "errorCode" - - # Filter out the already indexed documents. - # This is true when the document does not exist - result[error_code_key] == "document_not_found" - else - true - end - end) - result = - meili_post!( + meili_put( "/indexes/objects/documents", objects ) - if not Map.has_key?(result, "updateId") do - IO.puts("Failed to index: #{inspect(result)}") + with {:ok, res} <- result do + if not Map.has_key?(res, "updateId") do + IO.puts("\nFailed to index: #{inspect(result)}") + end + else + e -> IO.puts("\nFailed to index due to network error: #{inspect(e)}") end end) |> Stream.run() @@ -137,7 +123,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do def run(["stats"]) do start_pleroma() - result = meili_get!("/indexes/objects/stats") + {:ok, result} = meili_get("/indexes/objects/stats") IO.puts("Number of entries: #{result["numberOfDocuments"]}") IO.puts("Indexing? #{result["isIndexing"]}") end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index fa9e27b03..21b44de86 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -14,29 +14,50 @@ defmodule Pleroma.Search.Meilisearch do if is_nil(private_key), do: [], else: [{"X-Meili-API-Key", private_key}] end - def meili_get!(path) do + def meili_get(path) do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - {:ok, result} = + result = Pleroma.HTTP.get( Path.join(endpoint, path), meili_headers() ) - Jason.decode!(result.body) + with {:ok, res} <- result do + {:ok, Jason.decode!(res.body)} + end end - def meili_post!(path, params) do + def meili_post(path, params) do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - {:ok, result} = + result = Pleroma.HTTP.post( Path.join(endpoint, path), Jason.encode!(params), meili_headers() ) - Jason.decode!(result.body) + with {:ok, res} <- result do + {:ok, Jason.decode!(res.body)} + end + end + + def meili_put(path, params) do + endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) + + result = + Pleroma.HTTP.request( + :put, + Path.join(endpoint, path), + Jason.encode!(params), + meili_headers(), + [] + ) + + with {:ok, res} <- result do + {:ok, Jason.decode!(res.body)} + end end def meili_delete!(path) do @@ -57,34 +78,40 @@ defmodule Pleroma.Search.Meilisearch do offset = Keyword.get(options, :offset, 0) author = Keyword.get(options, :author) - result = - meili_post!( + res = + meili_post( "/indexes/objects/search", %{q: query, offset: offset, limit: limit} ) - hits = result["hits"] |> Enum.map(& &1["ap"]) + with {:ok, result} <- res do + hits = result["hits"] |> Enum.map(& &1["ap"]) - try do - hits - |> Activity.create_by_object_ap_id() - |> Activity.with_preloaded_object() - |> Activity.with_preloaded_object() - |> Activity.restrict_deactivated_users() - |> maybe_restrict_local(user) - |> maybe_restrict_author(author) - |> maybe_restrict_blocked(user) - |> maybe_fetch(user, query) - |> order_by([object: obj], desc: obj.data["published"]) - |> Pleroma.Repo.all() - rescue - _ -> maybe_fetch([], user, query) + try do + hits + |> Activity.create_by_object_ap_id() + |> Activity.with_preloaded_object() + |> Activity.with_preloaded_object() + |> Activity.restrict_deactivated_users() + |> maybe_restrict_local(user) + |> maybe_restrict_author(author) + |> maybe_restrict_blocked(user) + |> maybe_fetch(user, query) + |> order_by([object: obj], desc: obj.data["published"]) + |> Pleroma.Repo.all() + rescue + _ -> maybe_fetch([], user, query) + end end end def object_to_search_data(object) do + # Only index public or unlisted Notes if not is_nil(object) and object.data["type"] == "Note" and - Pleroma.Constants.as_public() in object.data["to"] do + not is_nil(object.data["content"]) and + (Pleroma.Constants.as_public() in object.data["to"] or + Pleroma.Constants.as_public() in object.data["cc"]) and + String.length(object.data["content"]) > 1 do data = object.data content_str = @@ -117,13 +144,17 @@ defmodule Pleroma.Search.Meilisearch do if activity.data["type"] == "Create" and maybe_search_data do result = - meili_post!( + meili_put( "/indexes/objects/documents", [maybe_search_data] ) - if not Map.has_key?(result, "updateId") do - Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") + with {:ok, res} <- result, + true <- Map.has_key?(res, "updateId") do + # Do nothing + else + _ -> + Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") end end end From 3a11e79de0c7092bf4fe0649e4ab1fcb53eb14a3 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 20 Dec 2021 17:46:23 +0300 Subject: [PATCH 0070/1008] Add config description for meilisearch --- config/description.exs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/config/description.exs b/config/description.exs index 3a2a65272..81fefaf89 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3475,5 +3475,40 @@ config :pleroma, :config_description, [ ] } ] + }, + %{ + group: :pleroma, + key: Pleroma.Search, + type: :group, + description: "General search settings.", + children: [ + %{ + key: :module, + type: :keyword, + description: "Selected search module.", + suggestion: [Pleroma.Search.DatabaseSearch, Pleroma.Search.Meilisearch] + } + ] + }, + %{ + group: :pleroma, + key: Pleroma.Search.Meilisearch, + type: :group, + description: "Meilisearch settings.", + children: [ + %{ + key: :url, + type: :string, + description: "Meilisearch URL.", + suggestion: ["http://127.0.0.1:7700/"] + }, + %{ + key: :private_key, + type: :string, + description: + "Private key for meilisearch authentication, or `nil` to disable private key authentication.", + suggestion: [nil] + } + ] } ] From 3412713c5b2fd24605b18933ef70de164ee14f2d Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 20 Dec 2021 18:16:33 +0300 Subject: [PATCH 0071/1008] Update search.md documentation with meilisearch indexing steps --- docs/configuration/search.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/configuration/search.md b/docs/configuration/search.md index c7e77d9c2..7dbbd3e17 100644 --- a/docs/configuration/search.md +++ b/docs/configuration/search.md @@ -60,6 +60,15 @@ have to do it one time, but it might take a while, depending on the amount of po consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2 million posts while idle and up to 7G while indexing initially, but your experience may be different). +The sequence of actions is as follows: + +1. First, change the configuration to use `Pleroma.Search.Meilisearch` as the search backend +2. Restart your instance, at this point it can be used while the search indexing is running, though search won't return anything +3. Start the initial indexing process (as described below with `index`), + and wait until the task says it sent everything from the database to index +4. Wait until everything is actually indexed (by checking with `stats` as described below), + at this point you don't have to do anything, just wait a while. + To start the initial indexing, run the `index` command: === "OTP" From 3179ed0921197a8a8f32a519c7d41dc09011024d Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 20 Dec 2021 18:48:52 +0300 Subject: [PATCH 0072/1008] Make chunk size configurable --- config/config.exs | 5 ++++- lib/mix/tasks/pleroma/search/meilisearch.ex | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index f7f3a1454..bcbc59b83 100644 --- a/config/config.exs +++ b/config/config.exs @@ -872,7 +872,10 @@ config :pleroma, ConcurrentLimiter, [ config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch -config :pleroma, Pleroma.Search.Meilisearch, url: "http://127.0.0.1:7700/", private_key: nil +config :pleroma, Pleroma.Search.Meilisearch, + url: "http://127.0.0.1:7700/", + private_key: nil, + initial_indexing_chunk_size: 100_000 # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 6730a99a9..021552f7b 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -38,7 +38,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do IO.puts("Created indices. Starting to insert posts.") - chunk_size = 10_000 + chunk_size = Pleroma.Config.get([Pleroma.Search.Meilisearch, :initial_indexing_chunk_size]) Pleroma.Repo.transaction( fn -> From 571533ae2618478f26db312e52265e143356debd Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 20 Dec 2021 19:05:59 +0300 Subject: [PATCH 0073/1008] Don't support meilisearch < 0.24.0, since it breaks things --- lib/mix/tasks/pleroma/search/meilisearch.ex | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 021552f7b..5098668ad 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -14,17 +14,29 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do def run(["index"]) do start_pleroma() + meili_version = + ( + {:ok, result} = meili_get("/version") + + result["pkgVersion"] + ) + + # The ranking rule syntax was changed but nothing about that is mentioned in the changelog + if not Version.match?(meili_version, ">= 0.24.0") do + raise "Meilisearch <0.24.0 not supported" + end + {:ok, _} = meili_post( "/indexes/objects/settings/ranking-rules", [ - "desc(published)", + "published:desc", "words", "exactness", "proximity", - "wordsPosition", "typo", - "attribute" + "attribute", + "sort" ] ) From 4f2637acc6c46ea39ae38e869903e7ffcc38b34d Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 20 Dec 2021 19:27:22 +0300 Subject: [PATCH 0074/1008] Add description for initial_indexing_chunk_size --- config/description.exs | 8 ++++++++ docs/configuration/search.md | 8 ++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/config/description.exs b/config/description.exs index 81fefaf89..cea4401ba 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3508,6 +3508,14 @@ config :pleroma, :config_description, [ description: "Private key for meilisearch authentication, or `nil` to disable private key authentication.", suggestion: [nil] + }, + %{ + key: :initial_indexing_chunk_size, + type: :int, + description: + "Amount of posts in a batch when running the initial indexing operation. Should probably not be more than 100000" <> + " since there's a limit on maximum insert size", + suggestion: [100_000] } ] } diff --git a/docs/configuration/search.md b/docs/configuration/search.md index 7dbbd3e17..a785a18ad 100644 --- a/docs/configuration/search.md +++ b/docs/configuration/search.md @@ -25,11 +25,15 @@ To use [meilisearch](https://www.meilisearch.com/), set the search module to `Pl > config :pleroma, Pleroma.Search, module: Pleroma.Search.Meilisearch -You then need to set the address of the meilisearch instance, and optionally the private key for authentication. +You then need to set the address of the meilisearch instance, and optionally the private key for authentication. You might +also want to change the `initial_indexing_chunk_size` to be smaller if you're server is not very powerful, but not higher than `100_000`, +because meilisearch will refuse to process it if it's too big. However, in general you want this to be as big as possible, because meilisearch +indexes faster when it can process many posts in a single batch. > config :pleroma, Pleroma.Search.Meilisearch, > url: "http://127.0.0.1:7700/", -> private_key: "private key" +> private_key: "private key", +> initial_indexing_chunk_size: 100_000 Information about setting up meilisearch can be found in the [official documentation](https://docs.meilisearch.com/learn/getting_started/installation.html). From 6f2f457751ea09507045e6dd5d5869a14befd3d1 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 20 Dec 2021 22:38:50 +0300 Subject: [PATCH 0075/1008] Add a search backend behaviour --- lib/pleroma/search/database_search.ex | 5 +++++ lib/pleroma/search/meilisearch.ex | 4 ++++ lib/pleroma/search/search_backend.ex | 17 +++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 lib/pleroma/search/search_backend.ex diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex index 5a8b8ca67..3735a5fab 100644 --- a/lib/pleroma/search/database_search.ex +++ b/lib/pleroma/search/database_search.ex @@ -13,6 +13,8 @@ defmodule Pleroma.Search.DatabaseSearch do import Ecto.Query + @behaviour Pleroma.Search.SearchBackend + def search(user, search_query, options \\ []) do index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin limit = Enum.min([Keyword.get(options, :limit), 40]) @@ -45,7 +47,10 @@ defmodule Pleroma.Search.DatabaseSearch do end end + @impl true def add_to_index(_activity), do: nil + + @impl true def remove_from_index(_object), do: nil def maybe_restrict_author(query, %User{} = author) do diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 21b44de86..33bbf8392 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -7,6 +7,8 @@ defmodule Pleroma.Search.Meilisearch do import Pleroma.Search.DatabaseSearch import Ecto.Query + @behaviour Pleroma.Search.SearchBackend + defp meili_headers do private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key]) @@ -139,6 +141,7 @@ defmodule Pleroma.Search.Meilisearch do end end + @impl true def add_to_index(activity) do maybe_search_data = object_to_search_data(activity.object) @@ -159,6 +162,7 @@ defmodule Pleroma.Search.Meilisearch do end end + @impl true def remove_from_index(object) do meili_delete!("/indexes/objects/documents/#{object.id}") end diff --git a/lib/pleroma/search/search_backend.ex b/lib/pleroma/search/search_backend.ex new file mode 100644 index 000000000..ed6bfd329 --- /dev/null +++ b/lib/pleroma/search/search_backend.ex @@ -0,0 +1,17 @@ +defmodule Pleroma.Search.SearchBackend do + @doc """ + Add the object associated with the activity to the search index. + + The whole activity is passed, to allow filtering on things such as scope. + """ + @callback add_to_index(activity :: Pleroma.Activity.t()) :: nil + + @doc """ + Remove the object from the index. + + Just the object, as opposed to the whole activity, is passed, since the object + is what contains the actual content and there is no need for fitlering when removing + from index. + """ + @callback remove_from_index(object :: Pleroma.Object.t()) :: nil +end From 2bc21c6f1884bae3226f760ed1da39dd9c5f2958 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 22 Jan 2022 15:23:11 +0300 Subject: [PATCH 0076/1008] Use oban for search indexing --- config/config.exs | 3 ++- lib/pleroma/search.ex | 15 ++++--------- lib/pleroma/workers/search_indexing_worker.ex | 21 +++++++++++++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 lib/pleroma/workers/search_indexing_worker.ex diff --git a/config/config.exs b/config/config.exs index bcbc59b83..3e2f0da3f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -571,7 +571,8 @@ config :pleroma, Oban, remote_fetcher: 2, attachments_cleanup: 1, new_users_digest: 1, - mute_expire: 5 + mute_expire: 5, + search_indexing: 1 ], plugins: [Oban.Plugins.Pruner], crontab: [ diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex index ae0b28c54..af858fc46 100644 --- a/lib/pleroma/search.ex +++ b/lib/pleroma/search.ex @@ -1,19 +1,12 @@ defmodule Pleroma.Search do - def add_to_index(activity) do - search_module = Pleroma.Config.get([Pleroma.Search, :module]) + alias Pleroma.Workers.SearchIndexingWorker - ConcurrentLimiter.limit(Pleroma.Search, fn -> - Task.start(fn -> search_module.add_to_index(activity) end) - end) + def add_to_index(activity) do + SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity.id}) end def remove_from_index(object) do - # Also delete from search index - search_module = Pleroma.Config.get([Pleroma.Search, :module]) - - ConcurrentLimiter.limit(Pleroma.Search, fn -> - Task.start(fn -> search_module.remove_from_index(object) end) - end) + SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object.id}) end def search(query, options) do diff --git a/lib/pleroma/workers/search_indexing_worker.ex b/lib/pleroma/workers/search_indexing_worker.ex new file mode 100644 index 000000000..43b7bad1e --- /dev/null +++ b/lib/pleroma/workers/search_indexing_worker.ex @@ -0,0 +1,21 @@ +defmodule Pleroma.Workers.SearchIndexingWorker do + use Pleroma.Workers.WorkerHelper, queue: "search_indexing" + + @impl Oban.Worker + + def perform(%Job{args: %{"op" => "add_to_index", "activity" => activity_id}}) do + activity = Pleroma.Activity.get_by_id_with_object(activity_id) + + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + search_module.add_to_index(activity) + end + + def perform(%Job{args: %{"op" => "remove_from_index", "object" => object_id}}) do + object = Pleroma.Object.get_by_id(object_id) + + search_module = Pleroma.Config.get([Pleroma.Search, :module]) + + search_module.remove_from_index(object) + end +end From d89dc5518b5c0eb232e7ac85ddd538f89c32606d Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 22 Jan 2022 16:31:32 +0300 Subject: [PATCH 0077/1008] Fix meilisearch tests and jobs for oban --- lib/pleroma/workers/search_indexing_worker.ex | 4 +++ test/pleroma/search/meilisearch_test.exs | 35 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/workers/search_indexing_worker.ex b/lib/pleroma/workers/search_indexing_worker.ex index 43b7bad1e..70a8d42d0 100644 --- a/lib/pleroma/workers/search_indexing_worker.ex +++ b/lib/pleroma/workers/search_indexing_worker.ex @@ -9,6 +9,8 @@ defmodule Pleroma.Workers.SearchIndexingWorker do search_module = Pleroma.Config.get([Pleroma.Search, :module]) search_module.add_to_index(activity) + + :ok end def perform(%Job{args: %{"op" => "remove_from_index", "object" => object_id}}) do @@ -17,5 +19,7 @@ defmodule Pleroma.Workers.SearchIndexingWorker do search_module = Pleroma.Config.get([Pleroma.Search, :module]) search_module.remove_from_index(object) + + :ok end end diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index 251388ea2..da614577f 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Search.MeilisearchTest do require Pleroma.Constants use Pleroma.DataCase + use Oban.Testing, repo: Pleroma.Repo import Pleroma.Factory import Tesla.Mock @@ -13,6 +14,7 @@ defmodule Pleroma.Search.MeilisearchTest do alias Pleroma.Search.Meilisearch alias Pleroma.Web.CommonAPI + alias Pleroma.Workers.SearchIndexingWorker setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -27,7 +29,8 @@ defmodule Pleroma.Search.MeilisearchTest do {Meilisearch, [:passthrough], [ add_to_index: fn a -> passthrough([a]) end, - remove_from_index: fn a -> passthrough([a]) end + remove_from_index: fn a -> passthrough([a]) end, + meili_put: fn u, a -> passthrough([u, a]) end ]} ], context, @@ -38,7 +41,7 @@ defmodule Pleroma.Search.MeilisearchTest do user = insert(:user) mock_global(fn - %{method: :post, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> + %{method: :put, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> assert match?( [%{"content" => "guys i just don't wanna leave the swamp"}], Jason.decode!(body) @@ -53,6 +56,15 @@ defmodule Pleroma.Search.MeilisearchTest do visibility: "public" }) + args = %{"op" => "add_to_index", "activity" => activity.id} + + assert_enqueued( + worker: SearchIndexingWorker, + args: args + ) + + assert :ok = perform_job(SearchIndexingWorker, args) + assert_called(Meilisearch.add_to_index(activity)) end @@ -60,26 +72,25 @@ defmodule Pleroma.Search.MeilisearchTest do user = insert(:user) Enum.each(["unlisted", "private", "direct"], fn visiblity -> - {:ok, _} = + {:ok, activity} = CommonAPI.post(user, %{ status: "guys i just don't wanna leave the swamp", visibility: visiblity }) + + Meilisearch.add_to_index(activity) + assert_not_called(Meilisearch.meili_put(:_)) end) history = call_history(Meilisearch) assert Enum.count(history) == 3 - - Enum.each(history, fn {_, _, return} -> - assert is_nil(return) - end) end test "deletes posts from index when deleted locally" do user = insert(:user) mock_global(fn - %{method: :post, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> + %{method: :put, url: "http://127.0.0.1:7700/indexes/objects/documents", body: body} -> assert match?( [%{"content" => "guys i just don't wanna leave the swamp"}], Jason.decode!(body) @@ -98,10 +109,16 @@ defmodule Pleroma.Search.MeilisearchTest do visibility: "public" }) - assert_called(Meilisearch.add_to_index(activity)) + args = %{"op" => "add_to_index", "activity" => activity.id} + assert_enqueued(worker: SearchIndexingWorker, args: args) + assert :ok = perform_job(SearchIndexingWorker, args) {:ok, _} = CommonAPI.delete(activity.id, user) + delete_args = %{"op" => "remove_from_index", "object" => activity.object.id} + assert_enqueued(worker: SearchIndexingWorker, args: delete_args) + assert :ok = perform_job(SearchIndexingWorker, delete_args) + assert_called(Meilisearch.remove_from_index(:_)) end end From 3387935e8354e32171fe6e28a8f96f49154acbb3 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 22 Jan 2022 16:52:06 +0300 Subject: [PATCH 0078/1008] Don't try removing deleted users and such from index as posts --- lib/pleroma/search.ex | 8 ++++---- lib/pleroma/web/activity_pub/side_effects.ex | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex index af858fc46..3b266e59b 100644 --- a/lib/pleroma/search.ex +++ b/lib/pleroma/search.ex @@ -1,12 +1,12 @@ defmodule Pleroma.Search do alias Pleroma.Workers.SearchIndexingWorker - def add_to_index(activity) do - SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity.id}) + def add_to_index(%Pleroma.Activity{id: activity_id}) do + SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity_id}) end - def remove_from_index(object) do - SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object.id}) + def remove_from_index(%Pleroma.Object{id: object_id}) do + SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object_id}) end def search(query, options) do diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 4762b5ac6..644e62630 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -330,7 +330,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do if result == :ok do Notification.create_notifications(object) - Pleroma.Search.remove_from_index(deleted_object) + # Only remove from index when deleting actual objects, not users or anything else + with %Pleroma.Object{} <- deleted_object do + Pleroma.Search.remove_from_index(deleted_object) + end {:ok, object, meta} else From fd2cfc80d2853c27f4d0c07631849da9b8d73e85 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 22 Jan 2022 17:17:43 +0300 Subject: [PATCH 0079/1008] Change search_indexing = 10 and retries for indexing = 2 --- config/config.exs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index 3e2f0da3f..ce9338014 100644 --- a/config/config.exs +++ b/config/config.exs @@ -572,7 +572,7 @@ config :pleroma, Oban, attachments_cleanup: 1, new_users_digest: 1, mute_expire: 5, - search_indexing: 1 + search_indexing: 10 ], plugins: [Oban.Plugins.Pruner], crontab: [ @@ -583,7 +583,8 @@ config :pleroma, Oban, config :pleroma, :workers, retries: [ federator_incoming: 5, - federator_outgoing: 5 + federator_outgoing: 5, + search_indexing: 2 ] config :pleroma, Pleroma.Formatter, From 79225d9b0adcd848502e5ba0bbbb295855a30ba0 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 22 Jan 2022 21:09:53 +0300 Subject: [PATCH 0080/1008] Actually, unlisted posts are indexed --- test/pleroma/search/meilisearch_test.exs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index da614577f..04a2d75d9 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -71,19 +71,23 @@ defmodule Pleroma.Search.MeilisearchTest do test "doesn't index posts that are not public" do user = insert(:user) - Enum.each(["unlisted", "private", "direct"], fn visiblity -> + Enum.each(["private", "direct"], fn visibility -> {:ok, activity} = CommonAPI.post(user, %{ status: "guys i just don't wanna leave the swamp", - visibility: visiblity + visibility: visibility }) - Meilisearch.add_to_index(activity) + args = %{"op" => "add_to_index", "activity" => activity.id} + + assert_enqueued(worker: SearchIndexingWorker, args: args) + assert :ok = perform_job(SearchIndexingWorker, args) + assert_not_called(Meilisearch.meili_put(:_)) end) history = call_history(Meilisearch) - assert Enum.count(history) == 3 + assert Enum.count(history) == 2 end test "deletes posts from index when deleted locally" do From 1e23f527e3e22108b402552a0766e488048ed3f4 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 22 Mar 2022 20:29:17 +0300 Subject: [PATCH 0081/1008] Change the meilisearch key auth to conform to 0.25.0 --- docs/configuration/search.md | 6 +++--- lib/mix/tasks/pleroma/search/meilisearch.ex | 14 ++++++++------ lib/pleroma/search/meilisearch.ex | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/configuration/search.md b/docs/configuration/search.md index a785a18ad..82217e5ee 100644 --- a/docs/configuration/search.md +++ b/docs/configuration/search.md @@ -47,15 +47,15 @@ you have to get the _private key_, which is actually used for authentication. === "OTP" ```sh - ./bin/pleroma_ctl search.meilisearch show-private-key + ./bin/pleroma_ctl search.meilisearch show-keys ``` === "From Source" ```sh - mix pleroma.search.meilisearch show-private-key + mix pleroma.search.meilisearch show-keys ``` -This is the key you actually put into your configuration file. +You will see a "Default Admin API Key", this is the key you actually put into your configuration file. ### Initial indexing diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 5098668ad..db56876fa 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do ) # The ranking rule syntax was changed but nothing about that is mentioned in the changelog - if not Version.match?(meili_version, ">= 0.24.0") do + if not Version.match?(meili_version, ">= 0.25.0") do raise "Meilisearch <0.24.0 not supported" end @@ -112,7 +112,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do meili_delete!("/indexes/objects/documents") end - def run(["show-private-key", master_key]) do + def run(["show-keys", master_key]) do start_pleroma() endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) @@ -120,15 +120,17 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do {:ok, result} = Pleroma.HTTP.get( Path.join(endpoint, "/keys"), - [{"X-Meili-API-Key", master_key}] + [{"Authorization", "Bearer #{master_key}"}] ) decoded = Jason.decode!(result.body) - if decoded["private"] do - IO.puts(decoded["private"]) + if decoded["results"] do + Enum.each(decoded["results"], fn %{"description" => desc, "key" => key} -> + IO.puts("#{desc}: #{key}") + end) else - IO.puts("Error fetching the key, check the master key is correct: #{inspect(decoded)}") + IO.puts("Error fetching the keys, check the master key is correct: #{inspect(decoded)}") end end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 33bbf8392..0f9182ffc 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Search.Meilisearch do private_key = Pleroma.Config.get([Pleroma.Search.Meilisearch, :private_key]) [{"Content-Type", "application/json"}] ++ - if is_nil(private_key), do: [], else: [{"X-Meili-API-Key", private_key}] + if is_nil(private_key), do: [], else: [{"Authorization", "Bearer #{private_key}"}] end def meili_get(path) do From 84608be87e2c5961a4deb9030307c978bf1168e5 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 22 Mar 2022 20:45:49 +0300 Subject: [PATCH 0082/1008] Change updateId to uid because apparently that's the new name --- lib/mix/tasks/pleroma/search/meilisearch.ex | 2 +- lib/pleroma/search/meilisearch.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index db56876fa..d4a83c3cd 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -91,7 +91,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do ) with {:ok, res} <- result do - if not Map.has_key?(res, "updateId") do + if not Map.has_key?(res, "uid") do IO.puts("\nFailed to index: #{inspect(result)}") end else diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 0f9182ffc..3db65f261 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -153,7 +153,7 @@ defmodule Pleroma.Search.Meilisearch do ) with {:ok, res} <- result, - true <- Map.has_key?(res, "updateId") do + true <- Map.has_key?(res, "uid") do # Do nothing else _ -> From b150e6f15e0f06c8e23c0ac66aeaf80eb2f8c31a Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 23 Mar 2022 11:36:01 +0300 Subject: [PATCH 0083/1008] Update meilisearch docs --- docs/configuration/search.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/configuration/search.md b/docs/configuration/search.md index 82217e5ee..f131948a7 100644 --- a/docs/configuration/search.md +++ b/docs/configuration/search.md @@ -37,8 +37,10 @@ indexes faster when it can process many posts in a single batch. Information about setting up meilisearch can be found in the [official documentation](https://docs.meilisearch.com/learn/getting_started/installation.html). -You probably want to start it with `MEILI_NO_ANALYTICS=true` and `MEILI_NO_CENTRY=true` environment variables, -to disable analytics. +You probably want to start it with `MEILI_NO_ANALYTICS=true` environment variable to disable analytics. +At least version 0.25.0 is required, but you are strongly adviced to use at least 0.26.0, as it introduces +the `--enable-auto-batching` option which drastically improves performance. Without this option, the search +is hardly usable on a somewhat big instance. ### Private key authentication (optional) From e20f74c71b078d706bc93632773f9b590d2fb018 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Fri, 26 Aug 2022 23:39:58 +0300 Subject: [PATCH 0084/1008] Remove duplicate function call --- lib/pleroma/search/meilisearch.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 3db65f261..53f8a2544 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -93,7 +93,6 @@ defmodule Pleroma.Search.Meilisearch do hits |> Activity.create_by_object_ap_id() |> Activity.with_preloaded_object() - |> Activity.with_preloaded_object() |> Activity.restrict_deactivated_users() |> maybe_restrict_local(user) |> maybe_restrict_author(author) From 119b2b847b76c7300bd71699d9f2e5676bdb0bb4 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 27 Aug 2022 00:09:37 +0300 Subject: [PATCH 0085/1008] Instead of checking string length, explicitly check for "" and "." --- lib/pleroma/search/meilisearch.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 53f8a2544..2e13b8407 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -112,7 +112,7 @@ defmodule Pleroma.Search.Meilisearch do not is_nil(object.data["content"]) and (Pleroma.Constants.as_public() in object.data["to"] or Pleroma.Constants.as_public() in object.data["cc"]) and - String.length(object.data["content"]) > 1 do + object.data["content"] not in ["", "."] do data = object.data content_str = @@ -127,7 +127,8 @@ defmodule Pleroma.Search.Meilisearch do trimmed end - if String.length(content) > 1 do + # Make sure we have a non-empty string + if content != "" do {:ok, published, _} = DateTime.from_iso8601(data["published"]) %{ From 102ebb42bdba1673da39a8fa8ed1662bc8565aa4 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 27 Aug 2022 00:19:08 +0300 Subject: [PATCH 0086/1008] Make search a callback --- lib/pleroma/search/database_search.ex | 1 + lib/pleroma/search/meilisearch.ex | 1 + lib/pleroma/search/search_backend.ex | 11 +++++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex index 3735a5fab..9a340abf1 100644 --- a/lib/pleroma/search/database_search.ex +++ b/lib/pleroma/search/database_search.ex @@ -15,6 +15,7 @@ defmodule Pleroma.Search.DatabaseSearch do @behaviour Pleroma.Search.SearchBackend + @impl true def search(user, search_query, options \\ []) do index_type = if Pleroma.Config.get([:database, :rum_enabled]), do: :rum, else: :gin limit = Enum.min([Keyword.get(options, :limit), 40]) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 2e13b8407..4e88169d2 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -75,6 +75,7 @@ defmodule Pleroma.Search.Meilisearch do ) end + @impl true def search(user, query, options \\ []) do limit = Enum.min([Keyword.get(options, :limit), 40]) offset = Keyword.get(options, :offset, 0) diff --git a/lib/pleroma/search/search_backend.ex b/lib/pleroma/search/search_backend.ex index ed6bfd329..a42e2f5f6 100644 --- a/lib/pleroma/search/search_backend.ex +++ b/lib/pleroma/search/search_backend.ex @@ -1,10 +1,17 @@ defmodule Pleroma.Search.SearchBackend do + @doc """ + Search statuses with a query, restricting to only those the user should have access to. + """ + @callback search(user :: Pleroma.User.t(), query :: String.t(), options :: [any()]) :: [ + Pleroma.Activity.t() + ] + @doc """ Add the object associated with the activity to the search index. The whole activity is passed, to allow filtering on things such as scope. """ - @callback add_to_index(activity :: Pleroma.Activity.t()) :: nil + @callback add_to_index(activity :: Pleroma.Activity.t()) :: :ok | {:error, any()} @doc """ Remove the object from the index. @@ -13,5 +20,5 @@ defmodule Pleroma.Search.SearchBackend do is what contains the actual content and there is no need for fitlering when removing from index. """ - @callback remove_from_index(object :: Pleroma.Object.t()) :: nil + @callback remove_from_index(object :: Pleroma.Object.t()) :: {:ok, any()} | {:error, any()} end From 5ac67632384bfb284ac51f2a450d41cf3913378a Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 27 Aug 2022 00:31:36 +0300 Subject: [PATCH 0087/1008] Make add_to_index and remove_from_index report errors --- lib/mix/tasks/pleroma/search/meilisearch.ex | 4 +-- lib/pleroma/search/meilisearch.ex | 27 +++++++++++-------- lib/pleroma/workers/search_indexing_worker.ex | 4 --- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index d4a83c3cd..72a558228 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -9,7 +9,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do import Ecto.Query import Pleroma.Search.Meilisearch, - only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete!: 1] + only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete: 1] def run(["index"]) do start_pleroma() @@ -109,7 +109,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do def run(["clear"]) do start_pleroma() - meili_delete!("/indexes/objects/documents") + meili_delete("/indexes/objects/documents") end def run(["show-keys", master_key]) do diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 4e88169d2..24789b00c 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -62,17 +62,16 @@ defmodule Pleroma.Search.Meilisearch do end end - def meili_delete!(path) do + def meili_delete(path) do endpoint = Pleroma.Config.get([Pleroma.Search.Meilisearch, :url]) - {:ok, _} = - Pleroma.HTTP.request( - :delete, - Path.join(endpoint, path), - "", - meili_headers(), - [] - ) + Pleroma.HTTP.request( + :delete, + Path.join(endpoint, path), + "", + meili_headers(), + [] + ) end @impl true @@ -155,16 +154,22 @@ defmodule Pleroma.Search.Meilisearch do with {:ok, res} <- result, true <- Map.has_key?(res, "uid") do - # Do nothing + # Added successfully + :ok else _ -> + # There was an error, report it Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") + {:error, result} end + else + # The post isn't something we can search, that's ok + :ok end end @impl true def remove_from_index(object) do - meili_delete!("/indexes/objects/documents/#{object.id}") + meili_delete("/indexes/objects/documents/#{object.id}") end end diff --git a/lib/pleroma/workers/search_indexing_worker.ex b/lib/pleroma/workers/search_indexing_worker.ex index 70a8d42d0..43b7bad1e 100644 --- a/lib/pleroma/workers/search_indexing_worker.ex +++ b/lib/pleroma/workers/search_indexing_worker.ex @@ -9,8 +9,6 @@ defmodule Pleroma.Workers.SearchIndexingWorker do search_module = Pleroma.Config.get([Pleroma.Search, :module]) search_module.add_to_index(activity) - - :ok end def perform(%Job{args: %{"op" => "remove_from_index", "object" => object_id}}) do @@ -19,7 +17,5 @@ defmodule Pleroma.Workers.SearchIndexingWorker do search_module = Pleroma.Config.get([Pleroma.Search, :module]) search_module.remove_from_index(object) - - :ok end end From 6256822afd368e5f6b410d47c5ff9b584e50a461 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 27 Aug 2022 01:11:50 +0300 Subject: [PATCH 0088/1008] Check for updateId, not uid --- lib/pleroma/search/meilisearch.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 24789b00c..0b90971b1 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -153,7 +153,7 @@ defmodule Pleroma.Search.Meilisearch do ) with {:ok, res} <- result, - true <- Map.has_key?(res, "uid") do + true <- Map.has_key?(res, "updateId") do # Added successfully :ok else From 5a39866388c411f2bcee9848352f8c420513f34f Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Sat, 27 Aug 2022 01:43:59 +0300 Subject: [PATCH 0089/1008] Specifically strip mentions for search indexing --- lib/mix/tasks/pleroma/search/meilisearch.ex | 1 + lib/pleroma/search/meilisearch.ex | 3 ++- priv/scrubbers/search_indexing.ex | 24 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 priv/scrubbers/search_indexing.ex diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 72a558228..8379a0c25 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -13,6 +13,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do def run(["index"]) do start_pleroma() + Pleroma.HTML.compile_scrubbers() meili_version = ( diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 0b90971b1..7af7f460a 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -122,7 +122,8 @@ defmodule Pleroma.Search.Meilisearch do end content = - with {:ok, scrubbed} <- FastSanitize.strip_tags(content_str), + with {:ok, scrubbed} <- + FastSanitize.Sanitizer.scrub(content_str, Pleroma.HTML.Scrubber.SearchIndexing), trimmed <- String.trim(scrubbed) do trimmed end diff --git a/priv/scrubbers/search_indexing.ex b/priv/scrubbers/search_indexing.ex new file mode 100644 index 000000000..02756ab79 --- /dev/null +++ b/priv/scrubbers/search_indexing.ex @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTML.Scrubber.SearchIndexing do + @moduledoc """ + An HTML scrubbing policy that scrubs things for searching. + """ + + require FastSanitize.Sanitizer.Meta + alias FastSanitize.Sanitizer.Meta + + # Explicitly remove mentions + def scrub({:a, attrs, children}) do + if(Enum.any?(attrs, fn {att, val} -> att == "class" and String.contains?(val, "mention") end), + do: nil, + # Strip the tag itself, leave only children (text, presumably) + else: children + ) + end + + Meta.strip_comments() + Meta.strip_everything_not_covered() +end From e4ac2a7cd69aa5e87d9dc277c0271e15466e3215 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 16 Dec 2022 02:56:32 -0500 Subject: [PATCH 0090/1008] Detail backup states --- lib/pleroma/ecto_enums.ex | 8 ++ lib/pleroma/user/backup.ex | 116 ++++++++++++++---- lib/pleroma/workers/backup_worker.ex | 2 +- .../20221216052127_add_state_to_backups.exs | 21 ++++ test/pleroma/user/backup_test.exs | 48 +++++++- 5 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 priv/repo/migrations/20221216052127_add_state_to_backups.exs diff --git a/lib/pleroma/ecto_enums.ex b/lib/pleroma/ecto_enums.ex index a4890b489..b346b39d6 100644 --- a/lib/pleroma/ecto_enums.ex +++ b/lib/pleroma/ecto_enums.ex @@ -27,3 +27,11 @@ defenum(Pleroma.DataMigration.State, failed: 4, manual: 5 ) + +defenum(Pleroma.User.Backup.State, + pending: 1, + running: 2, + complete: 3, + failed: 4, + invalid: 5 +) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 9df010605..74001f9c3 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -15,6 +15,7 @@ defmodule Pleroma.User.Backup do alias Pleroma.Bookmark alias Pleroma.Repo alias Pleroma.User + alias Pleroma.User.Backup.State alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.UserView @@ -25,12 +26,16 @@ defmodule Pleroma.User.Backup do field(:file_name, :string) field(:file_size, :integer, default: 0) field(:processed, :boolean, default: false) + field(:state, State, default: :invalid) + field(:processed_number, :integer, default: 0) belongs_to(:user, User, type: FlakeId.Ecto.CompatType) timestamps() end + @report_every 100 + def create(user, admin_id \\ nil) do with :ok <- validate_limit(user, admin_id), {:ok, backup} <- user |> new() |> Repo.insert() do @@ -46,7 +51,8 @@ defmodule Pleroma.User.Backup do %__MODULE__{ user_id: user.id, content_type: "application/zip", - file_name: name + file_name: name, + state: :pending } end @@ -109,27 +115,75 @@ defmodule Pleroma.User.Backup do def get(id), do: Repo.get(__MODULE__, id) + defp set_state(backup, state, processed_number \\ nil) do + struct = + %{state: state} + |> Pleroma.Maps.put_if_present(:processed_number, processed_number) + + backup + |> cast(struct, [:state, :processed_number]) + |> Repo.update() + end + def process(%__MODULE__{} = backup) do - with {:ok, zip_file} <- export(backup), - {:ok, %{size: size}} <- File.stat(zip_file), - {:ok, _upload} <- upload(backup, zip_file) do - backup - |> cast(%{file_size: size, processed: true}, [:file_size, :processed]) - |> Repo.update() + set_state(backup, :running, 0) + + current_pid = self() + + Task.Supervisor.async_nolink( + Pleroma.TaskSupervisor, + fn -> + with {:ok, zip_file} <- export(backup, current_pid), + {:ok, %{size: size}} <- File.stat(zip_file), + {:ok, _upload} <- upload(backup, zip_file) do + backup + |> cast( + %{ + file_size: size, + processed: true, + state: :complete + }, + [:file_size, :processed, :state] + ) + |> Repo.update() + + send(current_pid, :completed) + end + end + ) + + wait_backup(backup, backup.processed_number) + end + + defp wait_backup(backup, current_processed) do + receive do + {:progress, new_processed} -> + total_processed = current_processed + new_processed + + with {:ok, updated_backup} <- set_state(backup, :running, total_processed) do + wait_backup(updated_backup, total_processed) + else + _ -> wait_backup(backup, total_processed) + end + + :completed -> + {:ok, get(backup.id)} + after + 30_000 -> set_state(backup, :failed) end end @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] - def export(%__MODULE__{} = backup) do + def export(%__MODULE__{} = backup, caller_pid \\ nil) do backup = Repo.preload(backup, :user) name = String.trim_trailing(backup.file_name, ".zip") dir = dir(name) with :ok <- File.mkdir(dir), - :ok <- actor(dir, backup.user), - :ok <- statuses(dir, backup.user), - :ok <- likes(dir, backup.user), - :ok <- bookmarks(dir, backup.user), + :ok <- actor(dir, backup.user, caller_pid), + :ok <- statuses(dir, backup.user, caller_pid), + :ok <- likes(dir, backup.user, caller_pid), + :ok <- bookmarks(dir, backup.user, caller_pid), {:ok, zip_path} <- :zip.create(String.to_charlist(dir <> ".zip"), @files, cwd: dir), {:ok, _} <- File.rm_rf(dir) do {:ok, to_string(zip_path)} @@ -157,11 +211,12 @@ defmodule Pleroma.User.Backup do end end - defp actor(dir, user) do + defp actor(dir, user, caller_pid) do with {:ok, json} <- UserView.render("user.json", %{user: user}) |> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"}) |> Jason.encode() do + send(caller_pid, {:progress, 1}) File.write(Path.join(dir, "actor.json"), json) end end @@ -180,7 +235,9 @@ defmodule Pleroma.User.Backup do ) end - defp write(query, dir, name, fun) do + defp should_report?(num), do: rem(num, @report_every) == 0 + + defp write(query, dir, name, fun, caller_pid) do path = Path.join(dir, "#{name}.json") with {:ok, file} <- File.open(path, [:write, :utf8]), @@ -192,35 +249,41 @@ defmodule Pleroma.User.Backup do with {:ok, data} <- fun.(i), {:ok, str} <- Jason.encode(data), :ok <- IO.write(file, str <> ",\n") do + if should_report?(acc + 1) do + send(caller_pid, {:progress, @report_every}) + end + acc + 1 else _ -> acc end end) + send(caller_pid, {:progress, rem(total, @report_every)}) + with :ok <- :file.pwrite(file, {:eof, -2}, "\n],\n \"totalItems\": #{total}}") do File.close(file) end end end - defp bookmarks(dir, %{id: user_id} = _user) do + defp bookmarks(dir, %{id: user_id} = _user, caller_pid) do Bookmark |> where(user_id: ^user_id) |> join(:inner, [b], activity in assoc(b, :activity)) |> select([b, a], %{id: b.id, object: fragment("(?)->>'object'", a.data)}) - |> write(dir, "bookmarks", fn a -> {:ok, a.object} end) + |> write(dir, "bookmarks", fn a -> {:ok, a.object} end, caller_pid) end - defp likes(dir, user) do + defp likes(dir, user, caller_pid) do user.ap_id |> Activity.Queries.by_actor() |> Activity.Queries.by_type("Like") |> select([like], %{id: like.id, object: fragment("(?)->>'object'", like.data)}) - |> write(dir, "likes", fn a -> {:ok, a.object} end) + |> write(dir, "likes", fn a -> {:ok, a.object} end, caller_pid) end - defp statuses(dir, user) do + defp statuses(dir, user, caller_pid) do opts = %{} |> Map.put(:type, ["Create", "Announce"]) @@ -233,10 +296,15 @@ defmodule Pleroma.User.Backup do ] |> Enum.concat() |> ActivityPub.fetch_activities_query(opts) - |> write(dir, "outbox", fn a -> - with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do - {:ok, Map.delete(activity, "@context")} - end - end) + |> write( + dir, + "outbox", + fn a -> + with {:ok, activity} <- Transmogrifier.prepare_outgoing(a.data) do + {:ok, Map.delete(activity, "@context")} + end + end, + caller_pid + ) end end diff --git a/lib/pleroma/workers/backup_worker.ex b/lib/pleroma/workers/backup_worker.ex index 12ee70f00..a485ddb4b 100644 --- a/lib/pleroma/workers/backup_worker.ex +++ b/lib/pleroma/workers/backup_worker.ex @@ -51,7 +51,7 @@ defmodule Pleroma.Workers.BackupWorker do end @impl Oban.Worker - def timeout(_job), do: :timer.seconds(900) + def timeout(_job), do: :infinity defp has_email?(user) do not is_nil(user.email) and user.email != "" diff --git a/priv/repo/migrations/20221216052127_add_state_to_backups.exs b/priv/repo/migrations/20221216052127_add_state_to_backups.exs new file mode 100644 index 000000000..73b30fc35 --- /dev/null +++ b/priv/repo/migrations/20221216052127_add_state_to_backups.exs @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.AddStateToBackups do + use Ecto.Migration + + def up do + alter table(:backups) do + add(:state, :integer, default: 5) + add(:processed_number, :integer, default: 0) + end + end + + def down do + alter table(:backups) do + remove(:state) + remove(:processed_number) + end + end +end diff --git a/test/pleroma/user/backup_test.exs b/test/pleroma/user/backup_test.exs index 5c9b94000..a536b4a4a 100644 --- a/test/pleroma/user/backup_test.exs +++ b/test/pleroma/user/backup_test.exs @@ -39,7 +39,7 @@ defmodule Pleroma.User.BackupTest do assert_enqueued(worker: BackupWorker, args: args) backup = Backup.get(args["backup_id"]) - assert %Backup{user_id: ^user_id, processed: false, file_size: 0} = backup + assert %Backup{user_id: ^user_id, processed: false, file_size: 0, state: :pending} = backup end test "it return an error if the export limit is over" do @@ -59,7 +59,30 @@ defmodule Pleroma.User.BackupTest do assert {:ok, %Oban.Job{args: %{"backup_id" => backup_id} = args}} = Backup.create(user) assert {:ok, backup} = perform_job(BackupWorker, args) assert backup.file_size > 0 - assert %Backup{id: ^backup_id, processed: true, user_id: ^user_id} = backup + assert %Backup{id: ^backup_id, processed: true, user_id: ^user_id, state: :complete} = backup + + delete_job_args = %{"op" => "delete", "backup_id" => backup_id} + + assert_enqueued(worker: BackupWorker, args: delete_job_args) + assert {:ok, backup} = perform_job(BackupWorker, delete_job_args) + refute Backup.get(backup_id) + + email = Pleroma.Emails.UserEmail.backup_is_ready_email(backup) + + assert_email_sent( + to: {user.name, user.email}, + html_body: email.html_body + ) + end + + test "it updates states of the backup" do + clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) + %{id: user_id} = user = insert(:user) + + assert {:ok, %Oban.Job{args: %{"backup_id" => backup_id} = args}} = Backup.create(user) + assert {:ok, backup} = perform_job(BackupWorker, args) + assert backup.file_size > 0 + assert %Backup{id: ^backup_id, processed: true, user_id: ^user_id, state: :complete} = backup delete_job_args = %{"op" => "delete", "backup_id" => backup_id} @@ -148,7 +171,7 @@ defmodule Pleroma.User.BackupTest do Bookmark.create(user.id, status3.id) assert {:ok, backup} = user |> Backup.new() |> Repo.insert() - assert {:ok, path} = Backup.export(backup) + assert {:ok, path} = Backup.export(backup, self()) assert {:ok, zipfile} = :zip.zip_open(String.to_charlist(path), [:memory]) assert {:ok, {'actor.json', json}} = :zip.zip_get('actor.json', zipfile) @@ -230,6 +253,23 @@ defmodule Pleroma.User.BackupTest do File.rm!(path) end + test "it counts the correct number processed" do + user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"}) + + Enum.map(1..120, fn i -> + {:ok, status} = CommonAPI.post(user, %{status: "status #{i}"}) + CommonAPI.favorite(user, status.id) + Bookmark.create(user.id, status.id) + end) + + assert {:ok, backup} = user |> Backup.new() |> Repo.insert() + {:ok, backup} = Backup.process(backup) + + assert backup.processed_number == 1 + 120 + 120 + 120 + + Backup.delete(backup) + end + describe "it uploads and deletes a backup archive" do setup do clear_config([Pleroma.Upload, :base_url], "https://s3.amazonaws.com") @@ -246,7 +286,7 @@ defmodule Pleroma.User.BackupTest do Bookmark.create(user.id, status3.id) assert {:ok, backup} = user |> Backup.new() |> Repo.insert() - assert {:ok, path} = Backup.export(backup) + assert {:ok, path} = Backup.export(backup, self()) [path: path, backup: backup] end From bdd63d2a3a5fe76f8a8ac1f71cb8698be85bfef4 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 16 Dec 2022 14:10:48 -0500 Subject: [PATCH 0091/1008] Expose backup status via Pleroma API --- .../operations/pleroma_backup_operation.ex | 8 +++- .../web/pleroma_api/views/backup_view.ex | 10 +++++ .../pleroma_api/views/backup_view_test.exs | 39 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex index 45fa2b058..5655527e0 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex @@ -64,7 +64,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do content_type: %Schema{type: :string}, file_name: %Schema{type: :string}, file_size: %Schema{type: :integer}, - processed: %Schema{type: :boolean} + processed: %Schema{type: :boolean, description: "whether this backup has succeeded"}, + state: %Schema{type: :string, description: "the state of the backup", enum: ["pending", "running", "complete", "failed"]}, + processed_number: %Schema{type: :integer, description: "the number of records processed"} }, example: %{ "content_type" => "application/zip", @@ -72,7 +74,9 @@ defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do "https://cofe.fe:4000/media/backups/archive-foobar-20200908T164207-Yr7vuT5Wycv-sN3kSN2iJ0k-9pMo60j9qmvRCdDqIew.zip", "file_size" => 4105, "inserted_at" => "2020-09-08T16:42:07.000Z", - "processed" => true + "processed" => true, + "state" => "complete", + "processed_number": 20 } } end diff --git a/lib/pleroma/web/pleroma_api/views/backup_view.ex b/lib/pleroma/web/pleroma_api/views/backup_view.ex index d778590f0..02d891791 100644 --- a/lib/pleroma/web/pleroma_api/views/backup_view.ex +++ b/lib/pleroma/web/pleroma_api/views/backup_view.ex @@ -9,12 +9,22 @@ defmodule Pleroma.Web.PleromaAPI.BackupView do alias Pleroma.Web.CommonAPI.Utils def render("show.json", %{backup: %Backup{} = backup}) do + # To deal with records before the migration + state = + if backup.state == :invalid do + if backup.processed, do: :complete, else: :failed + else + backup.state + end + %{ id: backup.id, content_type: backup.content_type, url: download_url(backup), file_size: backup.file_size, processed: backup.processed, + state: to_string(state), + processed_number: backup.processed_number, inserted_at: Utils.to_masto_date(backup.inserted_at) } end diff --git a/test/pleroma/web/pleroma_api/views/backup_view_test.exs b/test/pleroma/web/pleroma_api/views/backup_view_test.exs index a86688bc4..6908463d6 100644 --- a/test/pleroma/web/pleroma_api/views/backup_view_test.exs +++ b/test/pleroma/web/pleroma_api/views/backup_view_test.exs @@ -15,4 +15,43 @@ defmodule Pleroma.Web.PleromaAPI.BackupViewTest do result = BackupView.render("show.json", backup: backup) assert result.id == backup.id end + + test "it renders the state and processed_number" do + user = insert(:user) + backup = Backup.new(user) + + result = BackupView.render("show.json", backup: backup) + assert result.state == to_string(backup.state) + assert result.processed_number == backup.processed_number + end + + test "it renders failed state with legacy records" do + backup = %Backup{ + id: 0, + content_type: "application/zip", + file_name: "dummy", + file_size: 1, + state: :invalid, + processed: true, + processed_number: 1, + inserted_at: NaiveDateTime.utc_now() + } + + result = BackupView.render("show.json", backup: backup) + assert result.state == "complete" + + backup = %Backup{ + id: 0, + content_type: "application/zip", + file_name: "dummy", + file_size: 1, + state: :invalid, + processed: false, + processed_number: 1, + inserted_at: NaiveDateTime.utc_now() + } + + result = BackupView.render("show.json", backup: backup) + assert result.state == "failed" + end end From 46ab97d72145a122cc0e0ada3cd9f0af5449bdf3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 16 Dec 2022 14:13:46 -0500 Subject: [PATCH 0092/1008] Simplify backup update clause --- lib/pleroma/user/backup.ex | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 74001f9c3..97b8718c1 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -160,11 +160,8 @@ defmodule Pleroma.User.Backup do {:progress, new_processed} -> total_processed = current_processed + new_processed - with {:ok, updated_backup} <- set_state(backup, :running, total_processed) do - wait_backup(updated_backup, total_processed) - else - _ -> wait_backup(backup, total_processed) - end + set_state(backup, :running, total_processed) + wait_backup(backup, total_processed) :completed -> {:ok, get(backup.id)} From a1b95922c5c4a4351b52c0f7a484a96efed08be9 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 16 Dec 2022 14:16:07 -0500 Subject: [PATCH 0093/1008] Fix compile error --- lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex index 5655527e0..ea3fc230a 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex @@ -76,7 +76,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do "inserted_at" => "2020-09-08T16:42:07.000Z", "processed" => true, "state" => "complete", - "processed_number": 20 + "processed_number" => 20 } } end From 070fbb89e1bc51669b03555b72c6852a769f1e15 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 16 Dec 2022 14:29:21 -0500 Subject: [PATCH 0094/1008] Lint --- .../api_spec/operations/pleroma_backup_operation.ex | 6 +++++- lib/pleroma/web/pleroma_api/views/backup_view.ex | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex index ea3fc230a..400f3825d 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex @@ -65,7 +65,11 @@ defmodule Pleroma.Web.ApiSpec.PleromaBackupOperation do file_name: %Schema{type: :string}, file_size: %Schema{type: :integer}, processed: %Schema{type: :boolean, description: "whether this backup has succeeded"}, - state: %Schema{type: :string, description: "the state of the backup", enum: ["pending", "running", "complete", "failed"]}, + state: %Schema{ + type: :string, + description: "the state of the backup", + enum: ["pending", "running", "complete", "failed"] + }, processed_number: %Schema{type: :integer, description: "the number of records processed"} }, example: %{ diff --git a/lib/pleroma/web/pleroma_api/views/backup_view.ex b/lib/pleroma/web/pleroma_api/views/backup_view.ex index 02d891791..20403aeee 100644 --- a/lib/pleroma/web/pleroma_api/views/backup_view.ex +++ b/lib/pleroma/web/pleroma_api/views/backup_view.ex @@ -11,11 +11,11 @@ defmodule Pleroma.Web.PleromaAPI.BackupView do def render("show.json", %{backup: %Backup{} = backup}) do # To deal with records before the migration state = - if backup.state == :invalid do - if backup.processed, do: :complete, else: :failed - else - backup.state - end + if backup.state == :invalid do + if backup.processed, do: :complete, else: :failed + else + backup.state + end %{ id: backup.id, From 7d3e4eaeb94a5381d56a3281e5813b7e0c63c8dd Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 18 Dec 2022 15:55:52 -0500 Subject: [PATCH 0095/1008] Log errors more extensively --- lib/pleroma/user/backup.ex | 105 +++++++++++++++++++++--------- test/pleroma/user/backup_test.exs | 50 ++++++++++++++ 2 files changed, 125 insertions(+), 30 deletions(-) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 97b8718c1..cb9a40ba1 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -9,6 +9,7 @@ defmodule Pleroma.User.Backup do import Ecto.Query import Pleroma.Web.Gettext + require Logger require Pleroma.Constants alias Pleroma.Activity @@ -130,48 +131,79 @@ defmodule Pleroma.User.Backup do current_pid = self() - Task.Supervisor.async_nolink( - Pleroma.TaskSupervisor, - fn -> - with {:ok, zip_file} <- export(backup, current_pid), - {:ok, %{size: size}} <- File.stat(zip_file), - {:ok, _upload} <- upload(backup, zip_file) do - backup - |> cast( - %{ - file_size: size, - processed: true, - state: :complete - }, - [:file_size, :processed, :state] - ) - |> Repo.update() + task = + Task.Supervisor.async_nolink( + Pleroma.TaskSupervisor, + __MODULE__, + :do_process, + [backup, current_pid] + ) - send(current_pid, :completed) - end - end - ) - - wait_backup(backup, backup.processed_number) + wait_backup(backup, backup.processed_number, task) end - defp wait_backup(backup, current_processed) do + def do_process(backup, current_pid) do + with {:ok, zip_file} <- export(backup, current_pid), + {:ok, %{size: size}} <- File.stat(zip_file), + {:ok, _upload} <- upload(backup, zip_file) do + backup + |> cast( + %{ + file_size: size, + processed: true, + state: :complete + }, + [:file_size, :processed, :state] + ) + |> Repo.update() + end + end + + defp wait_backup(backup, current_processed, task) do receive do {:progress, new_processed} -> total_processed = current_processed + new_processed set_state(backup, :running, total_processed) - wait_backup(backup, total_processed) + wait_backup(backup, total_processed, task) - :completed -> - {:ok, get(backup.id)} + {:DOWN, _ref, _proc, _pid, reason} -> + backup = get(backup.id) + + if reason != :normal do + Logger.error("Backup #{backup.id} process ended abnormally: #{inspect(reason)}") + + {:ok, backup} = set_state(backup, :failed) + + {:error, + %{ + backup: backup, + reason: :exit, + details: reason + }} + else + {:ok, backup} + end after - 30_000 -> set_state(backup, :failed) + 30_000 -> + Logger.error( + "Backup #{backup.id} timed out after no response for 30 seconds, terminating" + ) + + Task.Supervisor.terminate_child(Pleroma.TaskSupervisor, task.pid) + + {:ok, backup} = set_state(backup, :failed) + + {:error, + %{ + backup: backup, + reason: :timeout + }} end end @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] - def export(%__MODULE__{} = backup, caller_pid \\ nil) do + def export(%__MODULE__{} = backup, caller_pid) do backup = Repo.preload(backup, :user) name = String.trim_trailing(backup.file_name, ".zip") dir = dir(name) @@ -243,7 +275,12 @@ defmodule Pleroma.User.Backup do query |> Pleroma.Repo.chunk_stream(100) |> Enum.reduce(0, fn i, acc -> - with {:ok, data} <- fun.(i), + with {:ok, data} <- + (try do + fun.(i) + rescue + e -> {:error, e} + end), {:ok, str} <- Jason.encode(data), :ok <- IO.write(file, str <> ",\n") do if should_report?(acc + 1) do @@ -252,7 +289,15 @@ defmodule Pleroma.User.Backup do acc + 1 else - _ -> acc + {:error, e} -> + Logger.warn( + "Error processing backup item: #{inspect(e)}\n The item is: #{inspect(i)}" + ) + + acc + + _ -> + acc end end) diff --git a/test/pleroma/user/backup_test.exs b/test/pleroma/user/backup_test.exs index a536b4a4a..066bf6ba8 100644 --- a/test/pleroma/user/backup_test.exs +++ b/test/pleroma/user/backup_test.exs @@ -270,6 +270,56 @@ defmodule Pleroma.User.BackupTest do Backup.delete(backup) end + test "it handles errors" do + user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"}) + + Enum.map(1..120, fn i -> + {:ok, _status} = CommonAPI.post(user, %{status: "status #{i}"}) + end) + + assert {:ok, backup} = user |> Backup.new() |> Repo.insert() + + with_mock Pleroma.Web.ActivityPub.Transmogrifier, + [:passthrough], + prepare_outgoing: fn data -> + object = + data["object"] + |> Pleroma.Object.normalize(fetch: false) + |> Map.get(:data) + + data = data |> Map.put("object", object) + + if String.contains?(data["object"]["content"], "119"), + do: raise(%Postgrex.Error{}), + else: {:ok, data} + end do + {:ok, backup} = Backup.process(backup) + assert backup.processed + assert backup.state == :complete + assert backup.processed_number == 1 + 119 + + Backup.delete(backup) + end + end + + test "it handles unrecoverable exceptions" do + user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"}) + + assert {:ok, backup} = user |> Backup.new() |> Repo.insert() + + with_mock Backup, [:passthrough], do_process: fn _, _ -> raise "mock exception" end do + {:error, %{backup: backup, reason: :exit}} = Backup.process(backup) + + assert backup.state == :failed + end + + with_mock Backup, [:passthrough], do_process: fn _, _ -> Process.sleep(:timer.seconds(32)) end do + {:error, %{backup: backup, reason: :timeout}} = Backup.process(backup) + + assert backup.state == :failed + end + end + describe "it uploads and deletes a backup archive" do setup do clear_config([Pleroma.Upload, :base_url], "https://s3.amazonaws.com") From 179efd94677d1d30bdbbbbaafc899c8c908181d2 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 24 Dec 2022 00:17:17 -0500 Subject: [PATCH 0096/1008] Make backup parameters configurable --- config/config.exs | 4 +++- config/description.exs | 15 +++++++++++++++ lib/pleroma/user/backup.ex | 37 ++++++++++++++++++++++++++----------- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/config/config.exs b/config/config.exs index e41ec2f91..ecb592b9c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -874,7 +874,9 @@ config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthent config :pleroma, Pleroma.User.Backup, purge_after_days: 30, limit_days: 7, - dir: nil + dir: nil, + process_wait_time: 30_000, + process_chunk_size: 100 config :pleroma, ConcurrentLimiter, [ {Pleroma.Web.RichMedia.Helpers, [max_running: 5, max_waiting: 5]}, diff --git a/config/description.exs b/config/description.exs index bf4734426..996267558 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3394,6 +3394,21 @@ config :pleroma, :config_description, [ type: :integer, description: "Limit user to export not more often than once per N days", suggestions: [7] + }, + %{ + key: :process_wait_time, + type: :integer, + label: "Process Wait Time", + description: + "The amount of time to wait for backup to report progress, in milliseconds. If no progress is received from the backup job for that much time, terminate it and deem it failed.", + suggestions: [30_000] + }, + %{ + key: :process_chunk_size, + type: :integer, + label: "Process Chunk Size", + description: "The number of activities to fetch in the backup job for each chunk.", + suggestions: [100] } ] }, diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index cb9a40ba1..447fca2a1 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -35,8 +35,6 @@ defmodule Pleroma.User.Backup do timestamps() end - @report_every 100 - def create(user, admin_id \\ nil) do with :ok <- validate_limit(user, admin_id), {:ok, backup} <- user |> new() |> Repo.insert() do @@ -160,6 +158,8 @@ defmodule Pleroma.User.Backup do end defp wait_backup(backup, current_processed, task) do + wait_time = Pleroma.Config.get([__MODULE__, :process_wait_time]) + receive do {:progress, new_processed} -> total_processed = current_processed + new_processed @@ -175,6 +175,8 @@ defmodule Pleroma.User.Backup do {:ok, backup} = set_state(backup, :failed) + cleanup(backup) + {:error, %{ backup: backup, @@ -185,15 +187,17 @@ defmodule Pleroma.User.Backup do {:ok, backup} end after - 30_000 -> + wait_time -> Logger.error( - "Backup #{backup.id} timed out after no response for 30 seconds, terminating" + "Backup #{backup.id} timed out after no response for #{wait_time}ms, terminating" ) Task.Supervisor.terminate_child(Pleroma.TaskSupervisor, task.pid) {:ok, backup} = set_state(backup, :failed) + cleanup(backup) + {:error, %{ backup: backup, @@ -205,8 +209,7 @@ defmodule Pleroma.User.Backup do @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] def export(%__MODULE__{} = backup, caller_pid) do backup = Repo.preload(backup, :user) - name = String.trim_trailing(backup.file_name, ".zip") - dir = dir(name) + dir = backup_tempdir(backup) with :ok <- File.mkdir(dir), :ok <- actor(dir, backup.user, caller_pid), @@ -264,16 +267,28 @@ defmodule Pleroma.User.Backup do ) end - defp should_report?(num), do: rem(num, @report_every) == 0 + defp should_report?(num, chunk_size), do: rem(num, chunk_size) == 0 + + defp backup_tempdir(backup) do + name = String.trim_trailing(backup.file_name, ".zip") + dir(name) + end + + defp cleanup(backup) do + dir = backup_tempdir(backup) + File.rm_rf(dir) + end defp write(query, dir, name, fun, caller_pid) do path = Path.join(dir, "#{name}.json") + chunk_size = Pleroma.Config.get([__MODULE__, :process_chunk_size]) + with {:ok, file} <- File.open(path, [:write, :utf8]), :ok <- write_header(file, name) do total = query - |> Pleroma.Repo.chunk_stream(100) + |> Pleroma.Repo.chunk_stream(chunk_size, _returns_as = :one, timeout: :infinity) |> Enum.reduce(0, fn i, acc -> with {:ok, data} <- (try do @@ -283,8 +298,8 @@ defmodule Pleroma.User.Backup do end), {:ok, str} <- Jason.encode(data), :ok <- IO.write(file, str <> ",\n") do - if should_report?(acc + 1) do - send(caller_pid, {:progress, @report_every}) + if should_report?(acc + 1, chunk_size) do + send(caller_pid, {:progress, chunk_size}) end acc + 1 @@ -301,7 +316,7 @@ defmodule Pleroma.User.Backup do end end) - send(caller_pid, {:progress, rem(total, @report_every)}) + send(caller_pid, {:progress, rem(total, chunk_size)}) with :ok <- :file.pwrite(file, {:eof, -2}, "\n],\n \"totalItems\": #{total}}") do File.close(file) From c7dc5ce85c5b143df45031d5c87eee0689da3713 Mon Sep 17 00:00:00 2001 From: silverpill Date: Thu, 29 Dec 2022 01:12:32 +0000 Subject: [PATCH 0097/1008] TagValidator: Allow unrecognized Tag types --- .../web/activity_pub/object_validators/tag_validator.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex index 9f15f1981..ad968994e 100644 --- a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex @@ -68,6 +68,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do |> validate_required([:type, :name, :icon]) end + # Fallback + def changeset(struct, data), do: cast(struct, data, [:type, :name]) + def icon_changeset(struct, data) do struct |> cast(data, [:type, :url]) From 45646ff52cd045485c6be92713edcd0b37225bc3 Mon Sep 17 00:00:00 2001 From: silverpill Date: Thu, 29 Dec 2022 23:57:19 +0000 Subject: [PATCH 0098/1008] TagValidator: Add test for Link tag --- test/fixtures/fep-e232.json | 26 +++++++++++++++++++ .../web/activity_pub/transmogrifier_test.exs | 16 ++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test/fixtures/fep-e232.json diff --git a/test/fixtures/fep-e232.json b/test/fixtures/fep-e232.json new file mode 100644 index 000000000..98f339589 --- /dev/null +++ b/test/fixtures/fep-e232.json @@ -0,0 +1,26 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Create", + "actor": "https://example.org/users/alice", + "object": { + "id": "https://example.org/objects/10", + "type": "Note", + "attributedTo": "https://example.org/users/alice", + "content": "

test https://example.org/objects/9

", + "published": "2022-10-01T21:30:05.211215Z", + "tag": [ + { + "name": "https://example.org/objects/9", + "type": "Link", + "href": "https://example.org/objects/9", + "mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.org/users/alice/followers" + ] + } +} diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 6b4636d22..c0425f30c 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -123,6 +123,22 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity.data["context"] == object.data["context"] end + + # https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-e232.md + test "it accepts FEP-e232 link tags" do + insert(:user, ap_id: "https://example.org/users/alice") + + message = File.read!("test/fixtures/fep-e232.json") |> Jason.decode!() + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + object = Object.normalize(activity) + assert length(object.data["tag"]) == 1 + + tag = object.data["tag"] |> List.first() + assert tag["type"] == "Link" + assert tag["name"] == "https://example.org/objects/9" + end end describe "prepare outgoing" do From d5d7648789847b943caf339732aeeed11cd2728d Mon Sep 17 00:00:00 2001 From: kPherox Date: Wed, 1 Feb 2023 20:24:58 +0000 Subject: [PATCH 0099/1008] feat: build rel me tags with profile fields --- lib/pleroma/web/metadata/providers/rel_me.ex | 18 +++++++++++++++--- .../web/metadata/providers/rel_me_test.exs | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/metadata/providers/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex index f0bee85c8..e1890a050 100644 --- a/lib/pleroma/web/metadata/providers/rel_me.ex +++ b/lib/pleroma/web/metadata/providers/rel_me.ex @@ -8,12 +8,24 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do @impl Provider def build_tags(%{user: user}) do - bio_tree = Floki.parse_fragment!(user.bio) + profile_tree = + Floki.parse_fragment!(user.bio) + |> prepend_fields_tag(user.fields) - (Floki.attribute(bio_tree, "link[rel~=me]", "href") ++ - Floki.attribute(bio_tree, "a[rel~=me]", "href")) + (Floki.attribute(profile_tree, "link[rel~=me]", "href") ++ + Floki.attribute(profile_tree, "a[rel~=me]", "href")) |> Enum.map(fn link -> {:link, [rel: "me", href: link], []} end) end + + defp prepend_fields_tag(bio_tree, fields) do + fields + |> Enum.reduce(bio_tree, fn %{"value" => v}, tree -> + case Floki.parse_fragment(v) do + {:ok, [a | _]} -> [a | tree] + _ -> tree + end + end) + end end diff --git a/test/pleroma/web/metadata/providers/rel_me_test.exs b/test/pleroma/web/metadata/providers/rel_me_test.exs index cce4f3607..b1177edb7 100644 --- a/test/pleroma/web/metadata/providers/rel_me_test.exs +++ b/test/pleroma/web/metadata/providers/rel_me_test.exs @@ -11,10 +11,23 @@ defmodule Pleroma.Web.Metadata.Providers.RelMeTest do bio = ~s(https://some-link.com https://another-link.com ) - user = insert(:user, %{bio: bio}) + fields = [ + %{ + "name" => "profile", + "value" => ~S(http://profile.com) + }, + %{ + "name" => "like", + "value" => ~S(http://cofe.io) + }, + %{"name" => "foo", "value" => "bar"} + ] + + user = insert(:user, %{bio: bio, fields: fields}) assert RelMe.build_tags(%{user: user}) == [ {:link, [rel: "me", href: "http://some3.com"], []}, + {:link, [rel: "me", href: "http://profile.com"], []}, {:link, [rel: "me", href: "https://another-link.com"], []} ] end From 78d1105bffee7ece8a2b972d3cb58a6e41d86828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 19 Feb 2023 22:02:38 +0100 Subject: [PATCH 0100/1008] Fix down migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../20220319000000_add_status_to_notifications_enum.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs b/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs index 62c0afb63..c3bc85894 100644 --- a/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs +++ b/priv/repo/migrations/20220319000000_add_status_to_notifications_enum.exs @@ -36,7 +36,8 @@ defmodule Pleroma.Repo.Migrations.AddStatusToNotificationsEnum do 'reblog', 'favourite', 'pleroma:report', - 'poll + 'poll', + 'update' ) """ |> execute() From 5cfb0578a6845db377b5679ac05aa25ee5656211 Mon Sep 17 00:00:00 2001 From: silverpill Date: Fri, 30 Dec 2022 17:26:43 +0000 Subject: [PATCH 0101/1008] TagValidator: Drop unrecognized tags --- .../web/activity_pub/object_validators/tag_validator.ex | 7 +++++-- test/fixtures/fep-e232.json | 5 +++++ test/pleroma/web/activity_pub/transmogrifier_test.exs | 6 ++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex index ad968994e..cfd510c19 100644 --- a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex @@ -68,8 +68,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do |> validate_required([:type, :name, :icon]) end - # Fallback - def changeset(struct, data), do: cast(struct, data, [:type, :name]) + def changeset(struct, %{"type" => _} = data) do + struct + |> cast(data, []) + |> Map.put(:action, :ignore) + end def icon_changeset(struct, data) do struct diff --git a/test/fixtures/fep-e232.json b/test/fixtures/fep-e232.json index 98f339589..e9d12ae35 100644 --- a/test/fixtures/fep-e232.json +++ b/test/fixtures/fep-e232.json @@ -9,6 +9,11 @@ "content": "

test https://example.org/objects/9

", "published": "2022-10-01T21:30:05.211215Z", "tag": [ + { + "name": "@bob@example.net", + "type": "Mention", + "href": "https://example.net/users/bob" + }, { "name": "https://example.org/objects/9", "type": "Link", diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index c0425f30c..f76606479 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -124,8 +124,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity.data["context"] == object.data["context"] end - # https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-e232.md - test "it accepts FEP-e232 link tags" do + test "it drops link tags" do insert(:user, ap_id: "https://example.org/users/alice") message = File.read!("test/fixtures/fep-e232.json") |> Jason.decode!() @@ -136,8 +135,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert length(object.data["tag"]) == 1 tag = object.data["tag"] |> List.first() - assert tag["type"] == "Link" - assert tag["name"] == "https://example.org/objects/9" + assert tag["type"] == "Mention" end end From d83f16fe44210517ceec7bc7786728c1f4d81627 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 28 Feb 2023 22:16:01 -0500 Subject: [PATCH 0102/1008] Allow with_relationships param for blocks --- .../api_spec/operations/account_operation.ex | 2 +- .../controllers/account_controller.ex | 7 +++- .../controllers/account_controller_test.exs | 33 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 294590186..f2897a3a3 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -452,7 +452,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do operationId: "AccountController.blocks", description: "View your blocks. See also accounts/:id/{block,unblock}", security: [%{"oAuth" => ["read:blocks"]}], - parameters: pagination_params(), + parameters: [with_relationships_param() | pagination_params()], responses: %{ 200 => Operation.response("Accounts", "application/json", array_of_accounts()) } diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 02dfc552f..c313a0e97 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -540,7 +540,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do conn |> add_link_headers(users) - |> render("index.json", users: users, for: user, as: :user) + |> render("index.json", + users: users, + for: user, + as: :user, + embed_relationships: embed_relationships?(params) + ) end @doc "GET /api/v1/accounts/lookup" diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 958b7f76f..128e60b0a 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -2031,6 +2031,39 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert [%{"id" => ^id1}] = result end + test "list of blocks with with_relationships parameter" do + %{user: user, conn: conn} = oauth_access(["read:blocks"]) + %{id: id1} = other_user1 = insert(:user) + %{id: id2} = other_user2 = insert(:user) + %{id: id3} = other_user3 = insert(:user) + + {:ok, _, _} = User.follow(other_user1, user) + {:ok, _, _} = User.follow(other_user2, user) + {:ok, _, _} = User.follow(other_user3, user) + + {:ok, _} = User.block(user, other_user1) + {:ok, _} = User.block(user, other_user2) + {:ok, _} = User.block(user, other_user3) + + assert [ + %{ + "id" => ^id3, + "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + }, + %{ + "id" => ^id2, + "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + }, + %{ + "id" => ^id1, + "pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}} + } + ] = + conn + |> get("/api/v1/blocks?with_relationships=true") + |> json_response_and_validate_schema(200) + end + test "account lookup", %{conn: conn} do %{nickname: acct} = insert(:user, %{nickname: "nickname"}) %{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"}) From 1babd0798ff6b74a5049d8ddce19b9d131587d22 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 1 Mar 2023 00:40:44 -0500 Subject: [PATCH 0103/1008] Stop oban from retrying if validating errors occur when processing incoming data --- lib/pleroma/workers/receiver_worker.ex | 3 +++ test/pleroma/workers/receiver_worker_test.exs | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index 4f513b907..cf1bb62b4 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -13,6 +13,9 @@ defmodule Pleroma.Workers.ReceiverWorker do {:ok, res} else {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} + {:error, :already_present} -> {:cancel, :already_present} + {:error, {:validate_object, reason}} -> {:cancel, reason} + {:error, {:error, {:validate, reason}}} -> {:cancel, reason} {:error, {:reject, reason}} -> {:cancel, reason} e -> e end diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 283beee4d..3098a3da9 100644 --- a/test/pleroma/workers/receiver_worker_test.exs +++ b/test/pleroma/workers/receiver_worker_test.exs @@ -22,4 +22,31 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do }) end end + + test "it ignores ObjectValidator reject" do + params = + insert(:note_activity).data + |> Map.put("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id()) + |> Map.put("object", %{ + "type" => "Note", + "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id() + }) + + with_mock Pleroma.Web.ActivityPub.ObjectValidator, [:passthrough], + validate: fn _, _ -> {:error, %Ecto.Changeset{}} end do + assert {:cancel, {:error, %Ecto.Changeset{}}} = + ReceiverWorker.perform(%Oban.Job{ + args: %{"op" => "incoming_ap_doc", "params" => params} + }) + end + end + + test "it ignores duplicates" do + params = insert(:note_activity).data + + assert {:cancel, :already_present} = + ReceiverWorker.perform(%Oban.Job{ + args: %{"op" => "incoming_ap_doc", "params" => params} + }) + end end From bec4e5ac313e172d0077a9234cd4f733b980d180 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 1 Mar 2023 18:37:14 -0500 Subject: [PATCH 0104/1008] Fix FederatorTest --- test/pleroma/web/federator_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs index 41d1c5d5e..1ffe6aae1 100644 --- a/test/pleroma/web/federator_test.exs +++ b/test/pleroma/web/federator_test.exs @@ -133,7 +133,7 @@ defmodule Pleroma.Web.FederatorTest do assert {:ok, _activity} = ObanHelpers.perform(job) assert {:ok, job} = Federator.incoming_ap_doc(params) - assert {:error, :already_present} = ObanHelpers.perform(job) + assert {:cancel, :already_present} = ObanHelpers.perform(job) end test "rejects incoming AP docs with incorrect origin" do From a0ec66ce7e33a547e8b2337e97705ec8bdf957f5 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 1 Mar 2023 21:14:52 -0500 Subject: [PATCH 0105/1008] Make clear the test names --- test/pleroma/workers/receiver_worker_test.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 3098a3da9..acea0ae00 100644 --- a/test/pleroma/workers/receiver_worker_test.exs +++ b/test/pleroma/workers/receiver_worker_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do alias Pleroma.Workers.ReceiverWorker - test "it ignores MRF reject" do + test "it does not retry MRF reject" do params = insert(:note).data with_mock Pleroma.Web.ActivityPub.Transmogrifier, @@ -23,7 +23,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do end end - test "it ignores ObjectValidator reject" do + test "it does not retry ObjectValidator reject" do params = insert(:note_activity).data |> Map.put("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id()) @@ -41,7 +41,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do end end - test "it ignores duplicates" do + test "it does not retry duplicates" do params = insert(:note_activity).data assert {:cancel, :already_present} = From 787e30c5fd2f54a07db1bad84ef20aa9a4a406ab Mon Sep 17 00:00:00 2001 From: floatingghost Date: Sun, 4 Sep 2022 23:31:41 +0000 Subject: [PATCH 0106/1008] Allow reacting with remote emoji when they exist on the post (#200) Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/200 --- lib/pleroma/web/activity_pub/builder.ex | 79 +++++++++- .../emoji_react_validator.ex | 55 +++++-- lib/pleroma/web/activity_pub/utils.ex | 70 ++++++-- .../web/mastodon_api/views/status_view.ex | 12 +- .../controllers/emoji_reaction_controller.ex | 21 ++- .../pleroma_api/views/emoji_reaction_view.ex | 18 ++- .../emoji_reaction_controller_test.exs | 149 +++++++++++++++++- 7 files changed, 363 insertions(+), 41 deletions(-) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 532047599..4681b3dc9 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -16,6 +16,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI.ActivityDraft + alias Pleroma.Web.Endpoint require Pleroma.Constants @@ -54,13 +55,85 @@ defmodule Pleroma.Web.ActivityPub.Builder do {:ok, data, []} end + defp unicode_emoji_react(_object, data, emoji) do + data + |> Map.put("content", emoji) + |> Map.put("type", "EmojiReact") + end + + defp add_emoji_content(data, emoji, url) do + data + |> Map.put("content", Emoji.maybe_quote(emoji)) + |> Map.put("type", "EmojiReact") + |> Map.put("tag", [ + %{} + |> Map.put("id", url) + |> Map.put("type", "Emoji") + |> Map.put("name", Emoji.maybe_quote(emoji)) + |> Map.put( + "icon", + %{} + |> Map.put("type", "Image") + |> Map.put("url", url) + ) + ]) + end + + defp remote_custom_emoji_react( + %{data: %{"reactions" => existing_reactions}}, + data, + emoji + ) do + [emoji_code, instance] = String.split(Emoji.stripped_name(emoji), "@") + + matching_reaction = + Enum.find( + existing_reactions, + fn [name, _, url] -> + url = URI.parse(url) + url.host == instance && name == emoji_code + end + ) + + if matching_reaction do + [name, _, url] = matching_reaction + add_emoji_content(data, name, url) + else + {:error, "Could not react"} + end + end + + defp remote_custom_emoji_react(_object, _data, _emoji) do + {:error, "Could not react"} + end + + defp local_custom_emoji_react(data, emoji) do + with %{} = emojo <- Emoji.get(emoji) do + path = emojo |> Map.get(:file) + url = "#{Endpoint.url()}#{path}" + add_emoji_content(data, emojo.code, url) + else + _ -> {:error, "Emoji does not exist"} + end + end + + defp custom_emoji_react(object, data, emoji) do + if String.contains?(emoji, "@") do + remote_custom_emoji_react(object, data, emoji) + else + local_custom_emoji_react(data, emoji) + end + 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 data = - data - |> Map.put("content", emoji) - |> Map.put("type", "EmojiReact") + if Emoji.is_unicode_emoji?(emoji) do + unicode_emoji_react(object, data, emoji) + else + custom_emoji_react(object, data, emoji) + end {:ok, data, meta} end diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex index 0858281e5..54efe5c42 100644 --- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do use Ecto.Schema + alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes @@ -12,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @primary_key false + @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/ embedded_schema do quote do @@ -19,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields message_fields() activity_fields() + tag_fields() end end @@ -43,7 +46,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do def changeset(struct, data) do struct - |> cast(data, __schema__(:fields)) + |> cast(data, __schema__(:fields) -- [:tag]) + |> cast_embed(:tag) end defp fix(data) do @@ -53,12 +57,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do |> CommonFixes.fix_actor() |> CommonFixes.fix_activity_addressing() - with %Object{} = object <- Object.normalize(data["object"]) do - data - |> CommonFixes.fix_activity_context(object) - |> CommonFixes.fix_object_action_recipients(object) - else - _ -> data + data = + if Map.has_key?(data, "tag") do + data + else + Map.put(data, "tag", []) + end + + case Object.normalize(data["object"]) do + %Object{} = object -> + data + |> CommonFixes.fix_activity_context(object) + |> CommonFixes.fix_object_action_recipients(object) + + _ -> + data end end @@ -79,14 +92,37 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do defp fix_emoji_qualification(data), do: data + defp matches_shortcode?(nil), do: false + defp matches_shortcode?(s), do: Regex.match?(@emoji_regex, s) + defp validate_emoji(cng) do content = get_field(cng, :content) - if Pleroma.Emoji.is_unicode_emoji?(content) do + if Emoji.is_unicode_emoji?(content) || matches_shortcode?(content) do cng else cng - |> add_error(:content, "must be a single character emoji") + |> add_error(:content, "is not a valid emoji") + end + end + + defp maybe_validate_tag_presence(cng) do + content = get_field(cng, :content) + + if Emoji.is_unicode_emoji?(content) do + cng + else + tag = get_field(cng, :tag) + emoji_name = Emoji.stripped_name(content) + + case tag do + [%{name: ^emoji_name, type: "Emoji", icon: %{url: _}}] -> + cng + + _ -> + cng + |> add_error(:tag, "does not contain an Emoji tag") + end end end @@ -97,5 +133,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do |> validate_actor_presence() |> validate_object_presence() |> validate_emoji() + |> maybe_validate_tag_presence() end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index b898d6fe8..11f1b95c5 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -325,21 +325,29 @@ defmodule Pleroma.Web.ActivityPub.Utils do {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_emoji_reaction_to_object( - %Activity{data: %{"content" => emoji, "actor" => actor}}, + %Activity{data: %{"content" => emoji, "actor" => actor}} = activity, object ) do reactions = get_cached_emoji_reactions(object) + emoji = Pleroma.Emoji.stripped_name(emoji) + url = emoji_url(emoji, activity) new_reactions = - case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + case Enum.find_index(reactions, fn [candidate, _, candidate_url] -> + if is_nil(candidate_url) do + emoji == candidate + else + url == candidate_url + end + end) do nil -> - reactions ++ [[emoji, [actor]]] + reactions ++ [[emoji, [actor], url]] index -> List.update_at( reactions, index, - fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end + fn [emoji, users, url] -> [emoji, Enum.uniq([actor | users]), url] end ) end @@ -348,18 +356,40 @@ defmodule Pleroma.Web.ActivityPub.Utils do update_element_in_object("reaction", new_reactions, object, count) end + defp emoji_url( + name, + %Activity{ + data: %{ + "tag" => [ + %{"type" => "Emoji", "name" => name, "icon" => %{"url" => url}} + ] + } + } + ), + do: url + + defp emoji_url(_, _), do: nil + def emoji_count(reactions_list) do - Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end) + Enum.reduce(reactions_list, 0, fn [_, users, _], acc -> acc + length(users) end) end def remove_emoji_reaction_from_object( - %Activity{data: %{"content" => emoji, "actor" => actor}}, + %Activity{data: %{"content" => emoji, "actor" => actor}} = activity, object ) do + emoji = Pleroma.Emoji.stripped_name(emoji) reactions = get_cached_emoji_reactions(object) + url = emoji_url(emoji, activity) new_reactions = - case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do + case Enum.find_index(reactions, fn [candidate, _, candidate_url] -> + if is_nil(candidate_url) do + emoji == candidate + else + url == candidate_url + end + end) do nil -> reactions @@ -367,9 +397,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do List.update_at( reactions, index, - fn [emoji, users] -> [emoji, List.delete(users, actor)] end + fn [emoji, users, url] -> [emoji, List.delete(users, actor), url] end ) - |> Enum.reject(fn [_, users] -> Enum.empty?(users) end) + |> Enum.reject(fn [_, users, _] -> Enum.empty?(users) end) end count = emoji_count(new_reactions) @@ -489,17 +519,37 @@ defmodule Pleroma.Web.ActivityPub.Utils do def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do %{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id) + emoji = Pleroma.Emoji.maybe_quote(emoji) "EmojiReact" |> Activity.Queries.by_type() |> where(actor: ^ap_id) - |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji)) + |> custom_emoji_discriminator(emoji) |> Activity.Queries.by_object_id(object_ap_id) |> order_by([activity], fragment("? desc nulls last", activity.id)) |> limit(1) |> Repo.one() end + defp custom_emoji_discriminator(query, emoji) do + if String.contains?(emoji, "@") do + stripped = Pleroma.Emoji.stripped_name(emoji) + [name, domain] = String.split(stripped, "@") + domain_pattern = "%" <> domain <> "%" + emoji_pattern = Pleroma.Emoji.maybe_quote(name) + + query + |> where([activity], fragment("?->>'content' = ? + AND EXISTS ( + SELECT FROM jsonb_array_elements(?->'tag') elem + WHERE elem->>'id' ILIKE ? + )", activity.data, ^emoji_pattern, activity.data, ^domain_pattern)) + else + query + |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji)) + end + end + #### Announce-related helpers @doc """ diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 0a8c98b44..95efd9fd0 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -340,8 +340,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do opts[:for], Map.get(opts, :with_muted, false) ) - |> Stream.map(fn {emoji, users} -> - build_emoji_map(emoji, users, opts[:for]) + |> Stream.map(fn {emoji, users, url} -> + build_emoji_map(emoji, users, url, opts[:for]) end) |> Enum.to_list() @@ -702,11 +702,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - defp build_emoji_map(emoji, users, current_user) do + defp build_emoji_map(emoji, users, url, current_user) do %{ - name: emoji, + name: Pleroma.Web.PleromaAPI.EmojiReactionView.emoji_name(emoji, url), count: length(users), - me: !!(current_user && current_user.ap_id in users) + url: MediaProxy.url(url), + me: !!(current_user && current_user.ap_id in users), + account_ids: Enum.map(users, fn user -> User.get_cached_by_ap_id(user).id end) } end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index 78fd0b219..e095fa04a 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -50,29 +50,35 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do if not with_muted, do: User.cached_muted_users_ap_ids(user), else: [] end - filter_emoji = fn emoji, users -> + filter_emoji = fn emoji, users, url -> case Enum.reject(users, &(&1 in exclude_ap_ids)) do [] -> nil - users -> {emoji, users} + users -> {emoji, users, url} end end reactions |> Stream.map(fn - [emoji, users] when is_list(users) -> filter_emoji.(emoji, users) - {emoji, users} when is_list(users) -> filter_emoji.(emoji, users) + [emoji, users, url] when is_list(users) -> filter_emoji.(emoji, users, url) + {emoji, users, url} when is_list(users) -> filter_emoji.(emoji, users, url) + {emoji, users} when is_list(users) -> filter_emoji.(emoji, users, nil) _ -> nil end) |> Stream.reject(&is_nil/1) end defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do - Enum.filter(reactions, fn [e, _] -> e == emoji end) + Enum.filter(reactions, fn [e, _, _] -> e == emoji end) end defp filter(reactions, _), do: reactions def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do + emoji = + emoji + |> Pleroma.Emoji.fully_qualify_emoji() + |> Pleroma.Emoji.maybe_quote() + with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do activity = Activity.get_by_id(activity_id) @@ -83,6 +89,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do end def delete(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do + emoji = + emoji + |> Pleroma.Emoji.fully_qualify_emoji() + |> Pleroma.Emoji.maybe_quote() + with {:ok, _activity} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji) do activity = Activity.get_by_id(activity_id) diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex index 68ebd8292..67db1a5ad 100644 --- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex +++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex @@ -7,17 +7,31 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do alias Pleroma.Web.MastodonAPI.AccountView + def emoji_name(emoji, nil), do: emoji + alias Pleroma.Web.MediaProxy + + def emoji_name(emoji, url) do + url = URI.parse(url) + + if url.host == Pleroma.Web.Endpoint.host() do + emoji + else + "#{emoji}@#{url.host}" + end + end + def render("index.json", %{emoji_reactions: emoji_reactions} = opts) do render_many(emoji_reactions, __MODULE__, "show.json", opts) end - def render("show.json", %{emoji_reaction: {emoji, user_ap_ids}, user: user}) do + def render("show.json", %{emoji_reaction: {emoji, user_ap_ids, url}, user: user}) do users = fetch_users(user_ap_ids) %{ - name: emoji, + name: emoji_name(emoji, url), count: length(users), accounts: render(AccountView, "index.json", users: users, for: user), + url: MediaProxy.url(url), me: !!(user && user.ap_id in user_ap_ids) } end diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index 77c75b560..6af68cb52 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -17,23 +17,101 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) + note = insert(:note, user: user, data: %{"reactions" => [["👍", [other_user.ap_id], nil]]}) + activity = insert(:note_activity, note: note, user: user) result = conn |> assign(:user, other_user) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) - |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/☕") + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/\u26A0") |> json_response_and_validate_schema(200) - # We return the status, but this our implementation detail. assert %{"id" => id} = result assert to_string(activity.id) == id assert result["pleroma"]["emoji_reactions"] == [ - %{"name" => "☕", "count" => 1, "me" => true} + %{ + "name" => "👍", + "count" => 1, + "me" => true, + "url" => nil, + "account_ids" => [other_user.id] + }, + %{ + "name" => "\u26A0\uFE0F", + "count" => 1, + "me" => true, + "url" => nil, + "account_ids" => [other_user.id] + } ] + {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) + + ObanHelpers.perform_all() + + # Reacting with a custom emoji + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:dinosaur:") + |> json_response_and_validate_schema(200) + + assert %{"id" => id} = result + assert to_string(activity.id) == id + + assert result["pleroma"]["emoji_reactions"] == [ + %{ + "name" => "dinosaur", + "count" => 1, + "me" => true, + "url" => "http://localhost:4001/emoji/dino walking.gif", + "account_ids" => [other_user.id] + } + ] + + # Reacting with a remote emoji + note = + insert(:note, + user: user, + data: %{"reactions" => [["wow", [other_user.ap_id], "https://remote/emoji/wow"]]} + ) + + activity = insert(:note_activity, note: note, user: user) + + result = + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:") + |> json_response(200) + + assert result["pleroma"]["emoji_reactions"] == [ + %{ + "name" => "wow@remote", + "count" => 2, + "me" => true, + "url" => "https://remote/emoji/wow", + "account_ids" => [user.id, other_user.id] + } + ] + + # Reacting with a remote custom emoji that hasn't been reacted with yet + note = + insert(:note, + user: user + ) + + activity = insert(:note_activity, note: note, user: user) + + assert conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"])) + |> put("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:") + |> json_response(400) + # Reacting with a non-emoji assert conn |> assign(:user, other_user) @@ -46,8 +124,21 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) + note = + insert(:note, + user: user, + data: %{"reactions" => [["wow", [user.ap_id], "https://remote/emoji/wow"]]} + ) + + activity = insert(:note_activity, note: note, user: user) + + ObanHelpers.perform_all() + {:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _reaction_activity} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:") + + {:ok, _reaction_activity} = + CommonAPI.react_with_emoji(activity.id, other_user, ":wow@remote:") ObanHelpers.perform_all() @@ -60,11 +151,47 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do assert %{"id" => id} = json_response_and_validate_schema(result, 200) assert to_string(activity.id) == id + # Remove custom emoji + + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:dinosaur:") + + assert %{"id" => id} = json_response_and_validate_schema(result, 200) + assert to_string(activity.id) == id + ObanHelpers.perform_all() object = Object.get_by_ap_id(activity.data["object"]) - assert object.data["reaction_count"] == 0 + assert object.data["reaction_count"] == 2 + + # Remove custom remote emoji + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:wow@remote:") + |> json_response(200) + + assert result["pleroma"]["emoji_reactions"] == [ + %{ + "name" => "wow@remote", + "count" => 1, + "me" => false, + "url" => "https://remote/emoji/wow", + "account_ids" => [user.id] + } + ] + + # Remove custom remote emoji that hasn't been reacted with yet + assert conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) + |> delete("/api/v1/pleroma/statuses/#{activity.id}/reactions/:zoop@remote:") + |> json_response(400) end test "GET /api/v1/pleroma/statuses/:id/reactions", %{conn: conn} do @@ -181,7 +308,15 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") - assert [%{"name" => "🎅", "count" => 1, "accounts" => [represented_user], "me" => false}] = + assert [ + %{ + "name" => "🎅", + "count" => 1, + "accounts" => [represented_user], + "me" => false, + "url" => nil + } + ] = conn |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions/🎅") |> json_response_and_validate_schema(200) From 4b85d1c617e0d3d482dffe59e43c69d717d5a13f Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Sun, 18 Dec 2022 21:52:19 +0300 Subject: [PATCH 0107/1008] Allow custom emoji reactions: Fix tests, mixed custom and unicode reactions --- lib/pleroma/emoji.ex | 54 +++++++++++++++++ lib/pleroma/web/activity_pub/builder.ex | 6 +- .../object_validators/common_fields.ex | 7 +++ .../mastodon_api/views/notification_view.ex | 5 +- test/fixtures/custom-emoji-reaction.json | 28 +++++++++ .../emoji_react_handling_test.exs | 60 ++++++++++++++++++- .../web/activity_pub/side_effects_test.exs | 2 +- .../emoji_react_handling_test.exs | 53 +++++++++++++++- .../views/notification_view_test.exs | 3 +- .../mastodon_api/views/status_view_test.exs | 42 +++++++++---- 10 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 test/fixtures/custom-emoji-reaction.json diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index dd65d56ae..749b4f4ca 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -51,6 +51,15 @@ defmodule Pleroma.Emoji do @doc "Returns the path of the emoji `name`." @spec get(String.t()) :: String.t() | nil def get(name) do + name = + if String.starts_with?(name, ":") do + name + |> String.replace_leading(":", "") + |> String.replace_trailing(":", "") + else + name + end + case :ets.lookup(@ets, name) do [{_, path}] -> path _ -> nil @@ -139,6 +148,51 @@ defmodule Pleroma.Emoji do def is_unicode_emoji?(_), do: false + def stripped_name(name) when is_binary(name) do + name + |> String.replace_leading(":", "") + |> String.replace_trailing(":", "") + end + + def stripped_name(name), do: name + + def maybe_quote(name) when is_binary(name) do + if is_unicode_emoji?(name) do + name + else + if String.starts_with?(name, ":") do + name + else + ":#{name}:" + end + end + end + + def maybe_quote(name), do: name + + def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil + + def emoji_url(%{"type" => "EmojiReact", "content" => emoji, "tag" => tags}) do + tag = + tags + |> Enum.find(fn tag -> tag["type"] == "Emoji" && tag["name"] == stripped_name(emoji) end) + + if is_nil(tag) do + nil + else + tag + |> Map.get("icon") + |> Map.get("url") + end + end + + def emoji_url(_), do: nil + + def emoji_name_with_instance(name, url) do + url = url |> URI.parse() |> Map.get(:host) + "#{name}@#{url}" + end + emoji_qualification_map = emojis |> Enum.filter(&String.contains?(&1, "\uFE0F")) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 4681b3dc9..9553bde23 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -90,8 +90,10 @@ defmodule Pleroma.Web.ActivityPub.Builder do Enum.find( existing_reactions, fn [name, _, url] -> - url = URI.parse(url) - url.host == instance && name == emoji_code + if url != nil do + url = URI.parse(url) + url.host == instance && name == emoji_code + end end ) diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index 7b60c139a..904fdeced 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -58,10 +58,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do field(:like_count, :integer, default: 0) field(:announcement_count, :integer, default: 0) field(:inReplyTo, ObjectValidators.ObjectID) + field(:quoteUri, ObjectValidators.ObjectID) field(:url, ObjectValidators.Uri) field(:likes, {:array, ObjectValidators.ObjectID}, default: []) field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) end end + + defmacro tag_fields do + quote bind_quoted: binding() do + embeds_many(:tag, TagValidator) + end + end end diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index b5b5b2376..314246d3e 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.AdminAPI.ReportView alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MediaProxy alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView @@ -145,7 +146,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do end defp put_emoji(response, activity) do - Map.put(response, :emoji, activity.data["content"]) + response + |> Map.put(:emoji, activity.data["content"]) + |> Map.put(:emoji_url, MediaProxy.url(Pleroma.Emoji.emoji_url(activity.data))) end defp put_chat_message(response, activity, reading_user, opts) do diff --git a/test/fixtures/custom-emoji-reaction.json b/test/fixtures/custom-emoji-reaction.json new file mode 100644 index 000000000..003de0511 --- /dev/null +++ b/test/fixtures/custom-emoji-reaction.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "Hashtag": "as:Hashtag" + } + ], + "type": "Like", + "id": "https://misskey.local.live/likes/917ocsybgp", + "actor": "https://misskey.local.live/users/8x8yep20u2", + "object": "https://pleroma.local.live/objects/89937a53-2692-4631-bb62-770091267391", + "content": ":hanapog:", + "_misskey_reaction": ":hanapog:", + "tag": [ + { + "id": "https://misskey.local.live/emojis/hanapog", + "type": "Emoji", + "name": ":hanapog:", + "updated": "2022-06-07T12:00:05.773Z", + "icon": { + "type": "Image", + "mediaType": "image/png", + "url": "https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd" + } + } + ] +} diff --git a/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs index bbdb09c4c..9bb291a38 100644 --- a/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs @@ -38,16 +38,70 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactHandlingTest do 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 + test "it is valid when custom emoji is used", %{valid_emoji_react: valid_emoji_react} do without_emoji_content = valid_emoji_react - |> Map.put("content", "x") + |> Map.put("content", ":hello:") + |> Map.put("tag", [ + %{ + "type" => "Emoji", + "name" => ":hello:", + "icon" => %{"url" => "http://somewhere", "type" => "Image"} + } + ]) + + {:ok, _, _} = ObjectValidator.validate(without_emoji_content, []) + end + + test "it is not valid when custom emoji don't have a matching tag", %{ + valid_emoji_react: valid_emoji_react + } do + without_emoji_content = + valid_emoji_react + |> Map.put("content", ":hello:") + |> Map.put("tag", [ + %{ + "type" => "Emoji", + "name" => ":whoops:", + "icon" => %{"url" => "http://somewhere", "type" => "Image"} + } + ]) {:error, cng} = ObjectValidator.validate(without_emoji_content, []) refute cng.valid? - assert {:content, {"must be a single character emoji", []}} in cng.errors + assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors + end + + test "it is not valid when custom emoji have no tags", %{ + valid_emoji_react: valid_emoji_react + } do + without_emoji_content = + valid_emoji_react + |> Map.put("content", ":hello:") + |> Map.put("tag", []) + + {:error, cng} = ObjectValidator.validate(without_emoji_content, []) + + refute cng.valid? + + assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors + end + + test "it is not valid when custom emoji doesn't match a shortcode format", %{ + valid_emoji_react: valid_emoji_react + } do + without_emoji_content = + valid_emoji_react + |> Map.put("content", "hello") + |> Map.put("tag", []) + + {:error, cng} = ObjectValidator.validate(without_emoji_content, []) + + refute cng.valid? + + assert {:tag, {"does not contain an Emoji tag", []}} in cng.errors end end end diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index b24831e85..6820e23d0 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -453,7 +453,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do object = Object.get_by_ap_id(emoji_react.data["object"]) assert object.data["reaction_count"] == 1 - assert ["👌", [user.ap_id]] in object.data["reactions"] + assert ["👌", [user.ap_id], nil] in object.data["reactions"] end test "creates a notification", %{emoji_react: emoji_react, poster: poster} do diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs index 83bf59c6f..f2e1cefa3 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -34,7 +34,56 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do object = Object.get_by_ap_id(data["object"]) assert object.data["reaction_count"] == 1 - assert match?([["👌", _]], object.data["reactions"]) + assert match?([["👌", _, nil]], object.data["reactions"]) + end + + test "it works for incoming custom emoji reactions" do + user = insert(:user) + other_user = insert(:user, local: false) + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/custom-emoji-reaction.json") + |> Jason.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == other_user.ap_id + assert data["type"] == "EmojiReact" + assert data["id"] == "https://misskey.local.live/likes/917ocsybgp" + assert data["object"] == activity.data["object"] + assert data["content"] == ":hanapog:" + + assert data["tag"] == [ + %{ + "id" => "https://misskey.local.live/emojis/hanapog", + "type" => "Emoji", + "name" => "hanapog", + "updated" => "2022-06-07T12:00:05.773Z", + "icon" => %{ + "type" => "Image", + "url" => + "https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd" + } + } + ] + + object = Object.get_by_ap_id(data["object"]) + + assert object.data["reaction_count"] == 1 + + assert match?( + [ + [ + "hanapog", + _, + "https://misskey.local.live/files/webpublic-8f8a9768-7264-4171-88d6-2356aabeadcd" + ] + ], + object.data["reactions"] + ) end test "it works for incoming unqualified emoji reactions" do @@ -65,7 +114,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do object = Object.get_by_ap_id(data["object"]) assert object.data["reaction_count"] == 1 - assert match?([[^emoji, _]], object.data["reactions"]) + assert match?([[^emoji, _, _]], object.data["reactions"]) end test "it reject invalid emoji reactions" do diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index 6ea894691..e52985f8d 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -190,7 +190,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do emoji: "☕", account: AccountView.render("show.json", %{user: other_user, for: user}), status: StatusView.render("show.json", %{activity: activity, for: user}), - created_at: Utils.to_masto_date(notification.inserted_at) + created_at: Utils.to_masto_date(notification.inserted_at), + emoji_url: nil } test_notifications_rendering([notification], user, [expected]) diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index f76b115b7..a1f3e3e1d 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -35,16 +35,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do {:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"}) {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, ":dinosaur:") {:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵") {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:") + activity = Repo.get(Activity, activity.id) status = StatusView.render("show.json", activity: activity) assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 2, me: false}, - %{name: "🍵", count: 1, me: false} + %{name: "☕", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]}, + %{ + count: 2, + me: false, + name: "dinosaur", + url: "http://localhost:4001/emoji/dino walking.gif", + account_ids: [other_user.id, user.id] + }, + %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} ] status = StatusView.render("show.json", activity: activity, for: user) @@ -52,8 +62,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 2, me: true}, - %{name: "🍵", count: 1, me: false} + %{name: "☕", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]}, + %{ + count: 2, + me: true, + name: "dinosaur", + url: "http://localhost:4001/emoji/dino walking.gif", + account_ids: [other_user.id, user.id] + }, + %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]} ] end @@ -66,11 +83,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do |> Object.update_data(%{"reactions" => %{"☕" => [user.ap_id], "x" => 1}}) activity = Activity.get_by_id(activity.id) - status = StatusView.render("show.json", activity: activity, for: user) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: true} + %{name: "☕", count: 1, me: true, url: nil, account_ids: [user.id]} ] end @@ -90,7 +106,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status = StatusView.render("show.json", activity: activity) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: false} + %{name: "☕", count: 1, me: false, url: nil, account_ids: [other_user.id]} ] status = StatusView.render("show.json", activity: activity, for: user) @@ -102,19 +118,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status = StatusView.render("show.json", activity: activity) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 2, me: false} + %{ + name: "☕", + count: 2, + me: false, + url: nil, + account_ids: [third_user.id, other_user.id] + } ] status = StatusView.render("show.json", activity: activity, for: user) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: false} + %{name: "☕", count: 1, me: false, url: nil, account_ids: [third_user.id]} ] status = StatusView.render("show.json", activity: activity, for: other_user) assert status[:pleroma][:emoji_reactions] == [ - %{name: "☕", count: 1, me: true} + %{name: "☕", count: 1, me: true, url: nil, account_ids: [other_user.id]} ] end From 8d3b29aaba1a1879c9495ae15178b6e16ffc5faf Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Wed, 4 Jan 2023 21:13:51 +0300 Subject: [PATCH 0108/1008] Allow custom emoji reactions: add test for mixed emoji react, fix credo errors --- .../web/mastodon_api/views/notification_view.ex | 2 +- .../controllers/emoji_reaction_controller_test.exs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 314246d3e..2a51f3755 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -14,10 +14,10 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.AdminAPI.ReportView alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MediaProxy alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MediaProxy alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index 6af68cb52..d81b47a6b 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -76,7 +76,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do note = insert(:note, user: user, - data: %{"reactions" => [["wow", [other_user.ap_id], "https://remote/emoji/wow"]]} + data: %{ + "reactions" => [ + ["👍", [other_user.ap_id], nil], + ["wow", [other_user.ap_id], "https://remote/emoji/wow"] + ] + } ) activity = insert(:note_activity, note: note, user: user) @@ -89,6 +94,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do |> json_response(200) assert result["pleroma"]["emoji_reactions"] == [ + %{ + "account_ids" => [other_user.id], + "count" => 1, + "me" => false, + "name" => "👍", + "url" => nil + }, %{ "name" => "wow@remote", "count" => 2, From 5716654d127921d86b3aa8c9307ab9ac2ca6ab1b Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sun, 5 Mar 2023 01:32:40 +0100 Subject: [PATCH 0109/1008] Remove crypt(3) support This was used to support migration from GNU Social, which was used by at least shitposter.club, should be entirely irrelevant now. Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3030 Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3062 --- CHANGELOG.md | 1 + lib/pleroma/web/plugs/authentication_plug.ex | 8 ----- mix.exs | 1 - mix.lock | 1 - .../web/plugs/authentication_plug_test.exs | 30 ------------------- 5 files changed, 1 insertion(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c15cbe3c..491d676df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - rel="me" was missing its cache ### Removed +- Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) ## 2.5.1 diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index a7fd697b5..f912a1542 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -38,10 +38,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do def call(conn, _), do: conn - def checkpw(password, "$6" <> _ = password_hash) do - :crypt.crypt(password, password_hash) == password_hash - end - def checkpw(password, "$2" <> _ = password_hash) do # Handle bcrypt passwords for Mastodon migration Bcrypt.verify_pass(password, password_hash) @@ -60,10 +56,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do do_update_password(user, password) end - def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do - do_update_password(user, password) - end - def maybe_update_password(user, _), do: {:ok, user} defp do_update_password(user, password) do diff --git a/mix.exs b/mix.exs index 8aa18d5ee..0cc57d51c 100644 --- a/mix.exs +++ b/mix.exs @@ -150,7 +150,6 @@ defmodule Pleroma.Mixfile do {:sweet_xml, "~> 0.7.2"}, {:earmark, "~> 1.4.22"}, {:bbcode_pleroma, "~> 0.2.0"}, - {:crypt, "~> 1.0"}, {:cors_plug, "~> 2.0"}, {:web_push_encryption, "~> 0.3.1"}, {:swoosh, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index e5ce7b36a..f2336cf87 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,6 @@ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, "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": {:hex, :crypt, "1.0.1", "a3567e1c651a2ec42c6650d9f3ab789e0f12a508c060653a9bbb5fafe60f043c", [:rebar3], [], "hexpm", "968dffe321c7a5d9f9b4577c4a4ff56a1c26d1a8a2270eb22c7636a0b43d3982"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs index 41fdb93bc..b8acd01c5 100644 --- a/test/pleroma/web/plugs/authentication_plug_test.exs +++ b/test/pleroma/web/plugs/authentication_plug_test.exs @@ -70,28 +70,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do assert "$pbkdf2" <> _ = user.password_hash end - @tag :skip_on_mac - test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do - user = - insert(:user, - password_hash: - "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" - ) - - conn = - conn - |> assign(:auth_user, user) - |> assign(:auth_credentials, %{password: "password"}) - |> AuthenticationPlug.call(%{}) - - assert conn.assigns.user.id == conn.assigns.auth_user.id - assert conn.assigns.token == nil - assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) - - user = User.get_by_id(user.id) - assert "$pbkdf2" <> _ = user.password_hash - end - describe "checkpw/2" do test "check pbkdf2 hash" do hash = @@ -101,14 +79,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do refute AuthenticationPlug.checkpw("test-password1", hash) end - @tag :skip_on_mac - test "check sha512-crypt hash" do - hash = - "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" - - assert AuthenticationPlug.checkpw("password", hash) - end - test "check bcrypt hash" do hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS" From 8e072baed0744c8e4e29f54c608f3f29abb4fba8 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sun, 5 Mar 2023 08:41:15 +0100 Subject: [PATCH 0110/1008] docs: Be more explicit about the level of compatibility of OTP releases --- docs/installation/otp_en.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index 8c02201e6..f2812346b 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -2,15 +2,16 @@ {! backend/installation/otp_vs_from_source.include !} -This guide covers a installation using an OTP release. To install Pleroma from source, please check out the corresponding guide for your distro. +This guide covers a installation using OTP releases as built by the Pleroma project, it is meant as a fallback to distribution packages/recipes which are the preferred installation method. +To install Pleroma from source, please check out the corresponding guide for your distro. ## Pre-requisites -* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below +* A machine you have root access to running Debian GNU/Linux or compatible (eg. Ubuntu), or Alpine on `x86_64`, `aarch64` or `armv7l` CPU. If you are not sure what you are running see [Detecting flavour section](#detecting-flavour) below * A (sub)domain pointed to the machine -You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo su`/`su`. +You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo -i`/`su`. -While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine. +Similarly to other binaries, OTP releases tend to be only compatible with the distro they are built on, as such this guide focuses only on Debian/Ubuntu and Alpine. ### Detecting flavour @@ -19,7 +20,7 @@ Paste the following into the shell: arch="$(uname -m)";if [ "$arch" = "x86_64" ];then arch="amd64";elif [ "$arch" = "armv7l" ];then arch="arm";elif [ "$arch" = "aarch64" ];then arch="arm64";else echo "Unsupported arch: $arch">&2;fi;if getconf GNU_LIBC_VERSION>/dev/null;then libc_postfix="";elif [ "$(ldd 2>&1|head -c 9)" = "musl libc" ];then libc_postfix="-musl";elif [ "$(find /lib/libc.musl*|wc -l)" ];then libc_postfix="-musl";else echo "Unsupported libc">&2;fi;echo "$arch$libc_postfix" ``` -If your platform is supported the output will contain the flavour string, you will need it later. If not, this just means that we don't build releases for your platform, you can still try installing from source. +This should give your flavour string. If not this just means that we don't build releases for your platform, you can still try installing from source. ### Installing the required packages From f33e89765ab10350960c242beee2b9a86e38d984 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 6 Mar 2023 00:20:54 +0500 Subject: [PATCH 0111/1008] fix tag feeds: remote activities might not have a summary field --- lib/pleroma/web/feed/feed_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 449659f4b..0fcdf7c0e 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -72,7 +72,8 @@ defmodule Pleroma.Web.Feed.FeedView do def last_activity(activities), do: List.last(activities) - def activity_title(%{"content" => content, "summary" => summary} = data, opts \\ %{}) do + def activity_title(%{"content" => content} = data, opts \\ %{}) do + summary = Map.get(data, "summary", "") title = cond do summary != "" -> summary From d3f22d24f68df31539c952394a746ba281093c9f Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 6 Mar 2023 00:23:28 +0500 Subject: [PATCH 0112/1008] feed eex templates: use published field from @data, not @activity.data --- lib/pleroma/web/templates/feed/feed/_activity.atom.eex | 4 ++-- lib/pleroma/web/templates/feed/feed/_activity.rss.eex | 2 +- lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex | 4 ++-- lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex index 260338772..b774f7984 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex @@ -4,8 +4,8 @@ <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> <%= activity_description(@data) %> - <%= to_rfc3339(@activity.data["published"]) %> - <%= to_rfc3339(@activity.data["published"]) %> + <%= to_rfc3339(@data["published"]) %> + <%= to_rfc3339(@data["published"]) %> <%= activity_context(@activity) %> diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex index 5c8f35fe4..7de98f736 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex @@ -4,7 +4,7 @@ <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> <%= activity_description(@data) %> - <%= to_rfc2822(@activity.data["published"]) %> + <%= to_rfc2822(@data["published"]) %> <%= activity_context(@activity) %> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex index 25980c1e4..03c222975 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex @@ -7,8 +7,8 @@ <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> <%= activity_description(@data) %> - <%= to_rfc3339(@activity.data["published"]) %> - <%= to_rfc3339(@activity.data["published"]) %> + <%= to_rfc3339(@data["published"]) %> + <%= to_rfc3339(@data["published"]) %> <%= activity_context(@activity) %> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex index d582c83e8..1b8c34b87 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex @@ -4,7 +4,7 @@ <%= activity_context(@activity) %> <%= activity_context(@activity) %> - <%= to_rfc2822(@activity.data["published"]) %> + <%= to_rfc2822(@data["published"]) %> <%= activity_description(@data) %> <%= for attachment <- @data["attachment"] || [] do %> From 117a53b88eb0388ccfb8942d413ef4473acd73c7 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 6 Mar 2023 01:16:24 +0500 Subject: [PATCH 0113/1008] format feed_view.ex --- lib/pleroma/web/feed/feed_view.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 0fcdf7c0e..3cb717288 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -74,6 +74,7 @@ defmodule Pleroma.Web.Feed.FeedView do def activity_title(%{"content" => content} = data, opts \\ %{}) do summary = Map.get(data, "summary", "") + title = cond do summary != "" -> summary From 86ee4b72f3a54016bcc7e7f3ea8c386eca34e4b0 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 6 Mar 2023 02:30:52 +0500 Subject: [PATCH 0114/1008] modify Utils.scrub_html_and_truncate to take omission parameter --- lib/pleroma/web/metadata/utils.ex | 5 +++-- test/pleroma/web/metadata/utils_test.exs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 15414a988..80a8be9a2 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -30,12 +30,13 @@ defmodule Pleroma.Web.Metadata.Utils do |> scrub_html_and_truncate_object_field(object) end - def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do + def scrub_html_and_truncate(content, max_length \\ 200, omission \\ "...") + when is_binary(content) do content |> scrub_html |> Emoji.Formatter.demojify() |> HtmlEntities.decode() - |> Formatter.truncate(max_length) + |> Formatter.truncate(max_length, omission) end def scrub_html(content) when is_binary(content) do diff --git a/test/pleroma/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs index 85ef6033a..3daf852fb 100644 --- a/test/pleroma/web/metadata/utils_test.exs +++ b/test/pleroma/web/metadata/utils_test.exs @@ -72,7 +72,7 @@ defmodule Pleroma.Web.Metadata.UtilsTest do end end - describe "scrub_html_and_truncate/2" do + describe "scrub_html_and_truncate/3" do test "it returns text without encode HTML" do assert Utils.scrub_html_and_truncate("Pleroma's really cool!") == "Pleroma's really cool!" end From 141146d1f11a0a6581cc3161547ea597c92d56e9 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 6 Mar 2023 02:31:31 +0500 Subject: [PATCH 0115/1008] use scrub_html_and_truncate instead of scrub_html for feed item title Sometimes this truncated properly encoded HTML entities in the wrong place. The new flow calls scrub_html, removes emojis, decodes entities (a second time), truncates, and then re-encodes. Fixes #3045. --- lib/pleroma/web/feed/feed_view.ex | 5 +-- .../pleroma/web/feed/user_controller_test.exs | 44 +++++++++++++++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 3cb717288..9b7426792 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -83,9 +83,8 @@ defmodule Pleroma.Web.Feed.FeedView do end title - |> Pleroma.Web.Metadata.Utils.scrub_html() - |> Pleroma.Emoji.Formatter.demojify() - |> Formatter.truncate(opts[:max_length], opts[:omission]) + |> Pleroma.Web.Metadata.Utils.scrub_html_and_truncate(opts[:max_length], opts[:omission]) + |> HtmlEntities.encode() end def activity_description(data) do diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs index de32d3d4b..d3c4108de 100644 --- a/test/pleroma/web/feed/user_controller_test.exs +++ b/test/pleroma/web/feed/user_controller_test.exs @@ -57,9 +57,23 @@ defmodule Pleroma.Web.Feed.UserControllerTest do ) note_activity2 = insert(:note_activity, note: note2) + + note3 = + insert(:note, + user: user, + data: %{ + "content" => "This note tests whether HTML entities are truncated properly", + "summary" => "Won't, didn't fail", + "inReplyTo" => note_activity2.id + } + ) + + _note_activity3 = insert(:note_activity, note: note3) object = Object.normalize(note_activity, fetch: false) - [user: user, object: object, max_id: note_activity2.id] + encoded_title = FeedView.activity_title(note3.data) + + [user: user, object: object, max_id: note_activity2.id, encoded_title: encoded_title] end test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_id} do @@ -74,7 +88,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> SweetXml.parse() |> SweetXml.xpath(~x"//entry/title/text()"l) - assert activity_titles == ['2hu', '2hu & as'] + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -105,7 +119,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> SweetXml.parse() |> SweetXml.xpath(~x"//item/title/text()"l) - assert activity_titles == ['2hu', '2hu & as'] + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -176,6 +190,30 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> get("/users/#{user.nickname}/feed.rss") |> response(200) end + + test "does not mangle HTML entities midway", %{ + conn: conn, + user: user, + object: object, + encoded_title: encoded_title + } do + resp = + conn + |> put_req_header("accept", "application/atom+xml") + |> get(user_feed_path(conn, :feed, user.nickname)) + |> response(200) + + activity_titles = + resp + |> SweetXml.parse() + |> SweetXml.xpath(~x"//entry/title/text()"l) + + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] + assert resp =~ FeedView.escape(object.data["content"]) + assert resp =~ FeedView.escape(object.data["summary"]) + assert resp =~ FeedView.escape(object.data["context"]) + assert resp =~ encoded_title + end end # Note: see ActivityPubControllerTest for JSON format tests From 7b42ec5633df63987b75a6dca84daace30057ca7 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 6 Mar 2023 02:44:36 +0500 Subject: [PATCH 0116/1008] oops, remove unused import --- lib/pleroma/web/feed/feed_view.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 9b7426792..034722eb2 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.Feed.FeedView do use Phoenix.HTML use Pleroma.Web, :view - alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.Gettext From 8241eff05ba590f84895953913e1e4d1e702b79f Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Mon, 6 Mar 2023 23:33:57 +0500 Subject: [PATCH 0117/1008] remove static_fe pipeline for /users/:nickname/feed --- lib/pleroma/web/router.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ba1d64ab2..c1a690e28 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -835,8 +835,7 @@ defmodule Pleroma.Web.Router do end scope "/", Pleroma.Web do - # Note: html format is supported only if static FE is enabled - pipe_through([:accepts_html_xml, :static_fe]) + pipe_through([:accepts_html_xml]) get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed) end From ea07ec51efee8dbe6f0cfbdd21473db03af6c4ea Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 16 Nov 2020 17:13:42 +0100 Subject: [PATCH 0118/1008] Add support for Image objects --- CHANGELOG.md | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- .../web/activity_pub/object_validator.ex | 15 +++--- ...ator.ex => audio_image_video_validator.ex} | 15 ++++-- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- .../web/activity_pub/transmogrifier.ex | 2 +- lib/pleroma/web/activity_pub/utils.ex | 3 +- test/fixtures/hubzilla-actor.json | 1 + test/fixtures/hubzilla-create-image.json | 1 + .../transmogrifier/image_handling_test.exs | 50 +++++++++++++++++++ 10 files changed, 76 insertions(+), 16 deletions(-) rename lib/pleroma/web/activity_pub/object_validators/{audio_video_validator.ex => audio_image_video_validator.ex} (87%) create mode 100644 test/fixtures/hubzilla-actor.json create mode 100644 test/fixtures/hubzilla-create-image.json create mode 100644 test/pleroma/web/activity_pub/transmogrifier/image_handling_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c15cbe3c..ef550be3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed ### Added +- Support for Image activities, namely from Hubzilla ### Fixed diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1ab2db94a..f22756015 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -96,7 +96,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp increase_replies_count_if_reply(_create_data), do: :noop - @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note Page] + @object_types ~w[ChatMessage Question Answer Audio Video Image Event Article Note Page] @impl true def persist(%{"type" => type} = object, meta) when type in @object_types do with {:ok, object} <- Object.create(object) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 5bcd6da46..5e0d1aa8e 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator @@ -102,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, meta ) - when objtype in ~w[Question Answer Audio Video Event Article Note Page] do + when objtype in ~w[Question Answer Audio Video Image Event Article Note Page] do with {:ok, object_data} <- cast_and_apply_and_stringify_with_history(object), meta = Keyword.put(meta, :object_data, object_data), {:ok, create_activity} <- @@ -115,13 +115,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end def validate(%{"type" => type} = object, meta) - when type in ~w[Event Question Audio Video Article Note Page] do + when type in ~w[Event Question Audio Video Image Article Note Page] do validator = case type do "Event" -> EventValidator "Question" -> QuestionValidator - "Audio" -> AudioVideoValidator - "Video" -> AudioVideoValidator + "Audio" -> AudioImageVideoValidator + "Video" -> AudioImageVideoValidator + "Image" -> AudioImageVideoValidator "Article" -> ArticleNotePageValidator "Note" -> ArticleNotePageValidator "Page" -> ArticleNotePageValidator @@ -233,8 +234,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do AnswerValidator.cast_and_apply(object) end - def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do - AudioVideoValidator.cast_and_apply(object) + def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Image Video] do + AudioImageVideoValidator.cast_and_apply(object) end def cast_and_apply(%{"type" => "Event"} = object) do diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex similarity index 87% rename from lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex rename to lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex index 671a7ef0c..79ff76104 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do +defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do use Ecto.Schema alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes @@ -55,9 +55,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do url |> Enum.concat(mpeg_url["tag"] || []) |> Enum.find(fn - %{"mediaType" => mime_type} -> String.starts_with?(mime_type, ["video/", "audio/"]) - %{"mimeType" => mime_type} -> String.starts_with?(mime_type, ["video/", "audio/"]) - _ -> false + %{"mediaType" => mime_type} -> + String.starts_with?(mime_type, ["video/", "audio/", "image/"]) + + %{"mimeType" => mime_type} -> + String.starts_with?(mime_type, ["video/", "audio/", "image/"]) + + _ -> + false end) end @@ -110,7 +115,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do defp validate_data(data_cng) do data_cng - |> validate_inclusion(:type, ["Audio", "Video"]) + |> validate_inclusion(:type, ~w[Audio Image Video]) |> validate_required([:id, :actor, :attributedTo, :type, :context]) |> CommonValidations.validate_any_presence([:cc, :to]) |> CommonValidations.validate_fields_match([:actor, :attributedTo]) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index a2152b945..e19642d50 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -520,7 +520,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end def handle_object_creation(%{"type" => objtype} = object, _activity, meta) - when objtype in ~w[Audio Video Event Article Note Page] do + when objtype in ~w[Audio Video Image Event Article Note Page] do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do {:ok, object, meta} end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index e4c04da0d..3141f8437 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -447,7 +447,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data, options ) - when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page} do + when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note Page Image} do fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) object = diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index b898d6fe8..12a042a3e 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -31,7 +31,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do "Page", "Question", "Answer", - "Audio" + "Audio", + "Image" ] @strip_status_report_states ~w(closed resolved) @supported_report_states ~w(open closed resolved) diff --git a/test/fixtures/hubzilla-actor.json b/test/fixtures/hubzilla-actor.json new file mode 100644 index 000000000..445d6413c --- /dev/null +++ b/test/fixtures/hubzilla-actor.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://hub.somaton.com/apschema/v1.9"],"type":"Person","id":"https://hub.somaton.com/channel/testc6","preferredUsername":"testc6","name":"testc6 lala","updated":"2021-08-29T10:07:23Z","icon":{"type":"Image","mediaType":"image/png","updated":"2021-10-09T04:54:35Z","url":"https://hub.somaton.com/photo/profile/l/33","height":300,"width":300},"url":"https://hub.somaton.com/channel/testc6","publicKey":{"id":"https://hub.somaton.com/channel/testc6","owner":"https://hub.somaton.com/channel/testc6","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq5ep+6MhhaAiqZSd8nXe\nUAokXNgqTr/DjUic5VDudjQgvetchaiBUieBnqpJSPNNAvvf6Qs4eDW4w2JQeA6y\nqEplKrmb8l1EyhwXeFLDUGQdf0f6hg++x5mIrO6uX0tlQGU6nutvhItn6JMZc5GU\nv3C/UW0OfHCCdHSGZ/1nIqq1P98FqF0+PA1pvTHCkLr4kcKzfpmkLjsccUSq0FGh\nQF+paW9FU89o4hkaH/X3E/Ac7DL8zgcyt29KSj4eUIvjBIEPAMdRno345fiZ+QYr\nlYQYaBC2gvozjxtxl9MyfqjBRzfl9VDHzoDvMn5+LD5dCRB1zOESv/b3EpiHYqXl\nwiPzP9az8e8cw6D72n/Mlrf27yIuVAdwaGdbAwekjIQZHIDoP0XNnA5i31RLpEMI\nbNpH47ChtjxeilQZ3va6qIShYfGlndpy/rx4i4Yt4xIG+BbGb/dWo3AbtHi64fPZ\nMoLuR71sEBe7uAvalJ+lopxuQ2qLJpCInukQ13p/G/n9tVDwbfGyumzr5hHk7JoY\nN+JqH737MCZqb9dRDof+fju58GY1VzFjBph38sHYJh0ykA+2BzYU2+nT7CDXfKWA\nsmHhizp7haoPjl/yclZG5FJwg3oqHTD14dASUs+OI4K+Q//74wfb4/6E3CDyOkW3\nUj+8TPZooKulxtQ9ezergr0CAwEAAQ==\n-----END PUBLIC KEY-----\n"},"outbox":"https://hub.somaton.com/outbox/testc6","inbox":"https://hub.somaton.com/inbox/testc6","followers":"https://hub.somaton.com/followers/testc6","following":"https://hub.somaton.com/following/testc6","endpoints":{"sharedInbox":"https://hub.somaton.com/inbox"},"discoverable":false,"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"8d6dea03f04cbb7faaf43958a4cf39a115ff1c61c7febaa6154c463eab9a42c8","creator":"https://hub.somaton.com/channel/testc6","created":"2021-10-13T18:21:48Z","signatureValue":"N4CJBO2K/8v7KI97REyJXaSYOlLWscuEDlODDnjNYD1fbVQFO3s2JtqPcN2lVJvNTlW5HUze+owaAYNcvZe3mNm1iz05Xru3s8yRA8bNCdKBuWd/3zb3/JQVkbSb09D2PloeuoKBQmPIn+dNiTyFR0jxLsxCXXTomGKigWPtTOUIt52Dv9MFJ3jRZmfoykT9bHrAIVCASHoiluhTkPAzc6pt0lSyZd0D3X4J1K4/sLXa8HRoooMFu2dHWfqV4tyLU9WzofAhvnYg9tEbKCH42DIAbwDfjAeC4qL8xkqAlYWLvXYVGH76cZLdp9Zuv1p3NHqaPEJ85MbuaUkfnU75Bx/Fcfoi0pEieWRdFvMx5b/UFwGbJd6iSAO1zRbGYTPEMPWHzh0AEAaLeyY+g3ZmpNu88ujrIr8iJ1U4EkjOBn8ooxA5LaI2fXDiYC2NwRiAbY+xVtgJgvHDi9tXCdvzjZWfU/cgiwF/cYMbsB2BCyPRd+XZhudfXSOysFC4WYnawhiRVevba9lQ6rEP4FMepOGq4ZOSGzxgw2xNIXpu0IkrxX5mEv/ahEhDy1KGRIFc0GnPJrv3kMVxJrZ7SF8PNAGqftQBLkqQR+SEygs3XB4cd2DQ2lPeiMd8+Xv+lBjtzZtZAM/Y4CZCOdV9DHXDGNSKKFDzzna4QcUzQ+KRc8w="}} \ No newline at end of file diff --git a/test/fixtures/hubzilla-create-image.json b/test/fixtures/hubzilla-create-image.json new file mode 100644 index 000000000..9f0669bb7 --- /dev/null +++ b/test/fixtures/hubzilla-create-image.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://hub.somaton.com/apschema/v1.9"],"type":"Create","id":"https://hub.somaton.com/activity/452583b2-7e1f-4ac3-8334-ff666f134afe","diaspora:guid":"452583b2-7e1f-4ac3-8334-ff666f134afe","name":"daf82c18ef92a84cda72(1).jpg","published":"2021-10-12T21:28:26Z","actor":"https://hub.somaton.com/channel/testc6","object":{"type":"Image","name":"daf82c18ef92a84cda72(1).jpg","published":"2021-10-12T21:28:23Z","updated":"2021-10-12T21:28:23Z","attributedTo":"https://hub.somaton.com/channel/testc6","id":"https://hub.somaton.com/photo/452583b2-7e1f-4ac3-8334-ff666f134afe","url":[{"type":"Link","mediaType":"image/jpeg","href":"https://hub.somaton.com/photo/452583b2-7e1f-4ac3-8334-ff666f134afe-0.jpg","width":2200,"height":2200},{"type":"Link","mediaType":"image/jpeg","href":"https://hub.somaton.com/photo/452583b2-7e1f-4ac3-8334-ff666f134afe-1.jpg","width":1024,"height":1024},{"type":"Link","mediaType":"image/jpeg","href":"https://hub.somaton.com/photo/452583b2-7e1f-4ac3-8334-ff666f134afe-2.jpg","width":640,"height":640},{"type":"Link","mediaType":"image/jpeg","href":"https://hub.somaton.com/photo/452583b2-7e1f-4ac3-8334-ff666f134afe-3.jpg","width":320,"height":320},{"type":"Link","mediaType":"text/html","href":"https://hub.somaton.com/photos/testc6/image/452583b2-7e1f-4ac3-8334-ff666f134afe"}],"source":{"content":"[footer][zrl=https://hub.somaton.com/channel/testc6]testc6 lala[/zrl] posted [zrl=https://hub.somaton.com/photos/testc6/image/452583b2-7e1f-4ac3-8334-ff666f134afe]a new photo[/zrl] to [zrl=https://hub.somaton.com/photos/testc6/album/1e9b0d74-633e-4bd0-b37f-694bb0ed0145]test[/zrl][/footer]","mediaType":"text/bbcode"},"content":"","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://hub.somaton.com/followers/testc6"]},"target":{"type":"orderedCollection","name":"test","id":"https://hub.somaton.com/album/testc6/test"},"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://hub.somaton.com/followers/testc6"],"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"e0d077edccf262f02ed59ff67e91a5324ccaffc3d2b3f23793b4bd24cdbe70bb","creator":"https://hub.somaton.com/channel/testc6","created":"2021-10-13T18:39:05Z","signatureValue":"YYU0/17PqqUmLCn4oVS2N62rV1G9WQ+wLax2cI+EpMw/WOWKuVvtGrvhzciQ5ITXoh3scrZRYH8Bke1jDWkjL9YtjVD6TjMsv6f3OoO1vvMNgEfQfgZJ78QQt5MoLrT2mkRa35lSmVHkTDROKJPrwIAnpN6bDb577wZ63BsuBjqW7ca/E6oXSIr+meCXv3kqkyYDSz0ImYvVmki+OfX97xbYkQlzM06EgK1LZTHfuf4sk09hVfDDqVB9tHO4ObYQCYNiOWRHjA5S1Cw8WX1OQJ+GCQ8yxHmtiU3tJsxeYhxGs7VEmTLUvf/QZ0VTPumkd1CewdxzNGvAP3f9JCakuV7eyk88oqF+p7xxfxmBjLYbMTuhrcZIdUdMcjW9pENOYBbt+a+FhPsjbm8zVU3iKPqe/8UAvo01hGW7jiKJUm4qdcX3H3MExTLEFuz0NTeqxl4djlyGTT9KBqNouD+/oSSgwm6qeRZ5y3RsC27N0HRbg74qNXhhWQZVWQtHdSCHjAfHVPOSpjxpSPs7qkMLQ0vPsVsCsukZz8JCoXRo+JoKuaiaRgfiIRGNBO/XEicKMyu2JCU+UmkroiDJHy+4IfZRevnlneRa1jmu5KA/4xk5KU8l0I0Inap7TSPhv14Ex2sF89LkT8MbcDM3S3QL4urYsQj37zOKRDTFzE96TmI="}} \ No newline at end of file diff --git a/test/pleroma/web/activity_pub/transmogrifier/image_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/image_handling_test.exs new file mode 100644 index 000000000..b85f0a477 --- /dev/null +++ b/test/pleroma/web/activity_pub/transmogrifier/image_handling_test.exs @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.ImageHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Transmogrifier + + test "Hubzilla Image object" do + Tesla.Mock.mock(fn + %{url: "https://hub.somaton.com/channel/testc6"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/hubzilla-actor.json"), + headers: HttpRequestMock.activitypub_object_headers() + } + end) + + data = File.read!("test/fixtures/hubzilla-create-image.json") |> Poison.decode!() + + {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + + assert object = Object.normalize(activity, fetch: false) + + assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] + + assert object.data["cc"] == ["https://hub.somaton.com/followers/testc6"] + + assert object.data["attachment"] == [ + %{ + "mediaType" => "image/jpeg", + "type" => "Link", + "url" => [ + %{ + "height" => 2200, + "href" => + "https://hub.somaton.com/photo/452583b2-7e1f-4ac3-8334-ff666f134afe-0.jpg", + "mediaType" => "image/jpeg", + "type" => "Link", + "width" => 2200 + } + ] + } + ] + end +end From 2c2ea16b50a7b77b1d3f58722e45720dcf3c51b9 Mon Sep 17 00:00:00 2001 From: Alexander Tumin Date: Thu, 2 Mar 2023 10:09:13 +0300 Subject: [PATCH 0119/1008] Allow custom emoji reactions: Add pleroma_custom_emoji_reactions feature, review changes --- lib/pleroma/emoji.ex | 29 +++++++------- lib/pleroma/web/activity_pub/builder.ex | 30 +++++++------- .../object_validators/common_fields.ex | 7 ---- .../emoji_react_validator.ex | 18 +++------ lib/pleroma/web/activity_pub/utils.ex | 16 ++++---- .../web/mastodon_api/views/instance_view.ex | 1 + .../pleroma_api/views/emoji_reaction_view.ex | 3 +- .../views/notification_view_test.exs | 39 +++++++++++++++++++ 8 files changed, 83 insertions(+), 60 deletions(-) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 749b4f4ca..43a3447c3 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -51,14 +51,7 @@ defmodule Pleroma.Emoji do @doc "Returns the path of the emoji `name`." @spec get(String.t()) :: String.t() | nil def get(name) do - name = - if String.starts_with?(name, ":") do - name - |> String.replace_leading(":", "") - |> String.replace_trailing(":", "") - else - name - end + name = maybe_strip_name(name) case :ets.lookup(@ets, name) do [{_, path}] -> path @@ -148,13 +141,15 @@ defmodule Pleroma.Emoji do def is_unicode_emoji?(_), do: false - def stripped_name(name) when is_binary(name) do - name - |> String.replace_leading(":", "") - |> String.replace_trailing(":", "") - end + @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/ - def stripped_name(name), do: name + def is_custom_emoji?(s) when is_binary(s), do: Regex.match?(@emoji_regex, s) + + def is_custom_emoji?(_), do: false + + def maybe_strip_name(name) when is_binary(name), do: String.trim(name, ":") + + def maybe_strip_name(name), do: name def maybe_quote(name) when is_binary(name) do if is_unicode_emoji?(name) do @@ -173,9 +168,13 @@ defmodule Pleroma.Emoji do def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil def emoji_url(%{"type" => "EmojiReact", "content" => emoji, "tag" => tags}) do + emoji = maybe_strip_name(emoji) + tag = tags - |> Enum.find(fn tag -> tag["type"] == "Emoji" && tag["name"] == stripped_name(emoji) end) + |> Enum.find(fn tag -> + tag["type"] == "Emoji" && !is_nil(tag["name"]) && tag["name"] == emoji + end) if is_nil(tag) do nil diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 9553bde23..8eab3a241 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -62,21 +62,22 @@ defmodule Pleroma.Web.ActivityPub.Builder do end defp add_emoji_content(data, emoji, url) do + tag = [ + %{ + "id" => url, + "type" => "Emoji", + "name" => Emoji.maybe_quote(emoji), + "icon" => %{ + "type" => "Image", + "url" => url + } + } + ] + data |> Map.put("content", Emoji.maybe_quote(emoji)) |> Map.put("type", "EmojiReact") - |> Map.put("tag", [ - %{} - |> Map.put("id", url) - |> Map.put("type", "Emoji") - |> Map.put("name", Emoji.maybe_quote(emoji)) - |> Map.put( - "icon", - %{} - |> Map.put("type", "Image") - |> Map.put("url", url) - ) - ]) + |> Map.put("tag", tag) end defp remote_custom_emoji_react( @@ -84,7 +85,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do data, emoji ) do - [emoji_code, instance] = String.split(Emoji.stripped_name(emoji), "@") + [emoji_code, instance] = String.split(Emoji.maybe_strip_name(emoji), "@") matching_reaction = Enum.find( @@ -110,8 +111,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do end defp local_custom_emoji_react(data, emoji) do - with %{} = emojo <- Emoji.get(emoji) do - path = emojo |> Map.get(:file) + with %{file: path} = emojo <- Emoji.get(emoji) do url = "#{Endpoint.url()}#{path}" add_emoji_content(data, emojo.code, url) else diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index 904fdeced..7b60c139a 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -58,17 +58,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do field(:like_count, :integer, default: 0) field(:announcement_count, :integer, default: 0) field(:inReplyTo, ObjectValidators.ObjectID) - field(:quoteUri, ObjectValidators.ObjectID) field(:url, ObjectValidators.Uri) field(:likes, {:array, ObjectValidators.ObjectID}, default: []) field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) end end - - defmacro tag_fields do - quote bind_quoted: binding() do - embeds_many(:tag, TagValidator) - end - end end diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex index 54efe5c42..a0b82b325 100644 --- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex @@ -8,12 +8,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator import Ecto.Changeset import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations @primary_key false - @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/ embedded_schema do quote do @@ -21,7 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields message_fields() activity_fields() - tag_fields() + embeds_many(:tag, TagValidator) end end @@ -57,12 +57,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do |> CommonFixes.fix_actor() |> CommonFixes.fix_activity_addressing() - data = - if Map.has_key?(data, "tag") do - data - else - Map.put(data, "tag", []) - end + data = Map.put_new(data, "tag", []) case Object.normalize(data["object"]) do %Object{} = object -> @@ -92,13 +87,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do defp fix_emoji_qualification(data), do: data - defp matches_shortcode?(nil), do: false - defp matches_shortcode?(s), do: Regex.match?(@emoji_regex, s) - defp validate_emoji(cng) do content = get_field(cng, :content) - if Emoji.is_unicode_emoji?(content) || matches_shortcode?(content) do + if Emoji.is_unicode_emoji?(content) || Emoji.is_custom_emoji?(content) do cng else cng @@ -113,7 +105,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do cng else tag = get_field(cng, :tag) - emoji_name = Emoji.stripped_name(content) + emoji_name = Emoji.maybe_strip_name(content) case tag do [%{name: ^emoji_name, type: "Emoji", icon: %{url: _}}] -> diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 11f1b95c5..db7f79dac 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -329,8 +329,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do object ) do reactions = get_cached_emoji_reactions(object) - emoji = Pleroma.Emoji.stripped_name(emoji) - url = emoji_url(emoji, activity) + emoji = Pleroma.Emoji.maybe_strip_name(emoji) + url = maybe_emoji_url(emoji, activity) new_reactions = case Enum.find_index(reactions, fn [candidate, _, candidate_url] -> @@ -356,7 +356,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do update_element_in_object("reaction", new_reactions, object, count) end - defp emoji_url( + defp maybe_emoji_url( name, %Activity{ data: %{ @@ -368,7 +368,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do ), do: url - defp emoji_url(_, _), do: nil + defp maybe_emoji_url(_, _), do: nil def emoji_count(reactions_list) do Enum.reduce(reactions_list, 0, fn [_, users, _], acc -> acc + length(users) end) @@ -378,9 +378,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do %Activity{data: %{"content" => emoji, "actor" => actor}} = activity, object ) do - emoji = Pleroma.Emoji.stripped_name(emoji) + emoji = Pleroma.Emoji.maybe_strip_name(emoji) reactions = get_cached_emoji_reactions(object) - url = emoji_url(emoji, activity) + url = maybe_emoji_url(emoji, activity) new_reactions = case Enum.find_index(reactions, fn [candidate, _, candidate_url] -> @@ -533,9 +533,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do defp custom_emoji_discriminator(query, emoji) do if String.contains?(emoji, "@") do - stripped = Pleroma.Emoji.stripped_name(emoji) + stripped = Pleroma.Emoji.maybe_strip_name(emoji) [name, domain] = String.split(stripped, "@") - domain_pattern = "%" <> domain <> "%" + domain_pattern = "%/" <> domain <> "/%" emoji_pattern = Pleroma.Emoji.maybe_quote(name) query diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index abf7f29ab..efd2a0af6 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -92,6 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "safe_dm_mentions" end, "pleroma_emoji_reactions", + "pleroma_custom_emoji_reactions", "pleroma_chat_messages", if Config.get([:instance, :show_reactions]) do "exposable_reactions" diff --git a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex index 67db1a5ad..6df4ab9d0 100644 --- a/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex +++ b/lib/pleroma/web/pleroma_api/views/emoji_reaction_view.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do alias Pleroma.Web.MastodonAPI.AccountView def emoji_name(emoji, nil), do: emoji - alias Pleroma.Web.MediaProxy def emoji_name(emoji, url) do url = URI.parse(url) @@ -31,7 +30,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionView do name: emoji_name(emoji, url), count: length(users), accounts: render(AccountView, "index.json", users: users, for: user), - url: MediaProxy.url(url), + url: Pleroma.Web.MediaProxy.url(url), me: !!(user && user.ap_id in user_ap_ids) } end diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index e52985f8d..ddbe4557f 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -197,6 +197,45 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do test_notifications_rendering([notification], user, [expected]) end + test "EmojiReact custom emoji notification" do + user = insert(:user) + other_user = insert(:user) + + note = + insert(:note, + user: user, + data: %{ + "reactions" => [ + ["👍", [user.ap_id], nil], + ["dinosaur", [user.ap_id], "http://localhost:4001/emoji/dino walking.gif"] + ] + } + ) + + activity = insert(:note_activity, note: note, user: user) + + {:ok, _activity} = CommonAPI.react_with_emoji(activity.id, other_user, "dinosaur") + + activity = Repo.get(Activity, activity.id) + + [notification] = Notification.for_user(user) + + assert notification + + expected = %{ + id: to_string(notification.id), + pleroma: %{is_seen: false, is_muted: false}, + type: "pleroma:emoji_reaction", + emoji: ":dinosaur:", + account: AccountView.render("show.json", %{user: other_user, for: user}), + status: StatusView.render("show.json", %{activity: activity, for: user}), + created_at: Utils.to_masto_date(notification.inserted_at), + emoji_url: "http://localhost:4001/emoji/dino walking.gif" + } + + test_notifications_rendering([notification], user, [expected]) + end + test "Poll notification" do user = insert(:user) activity = insert(:question_activity, user: user) From d18441e9c41724de0ba9f08db90985cfdf83fd1a Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 12 Mar 2023 16:04:43 +0000 Subject: [PATCH 0120/1008] Indicate in changelog that removal of crypt is breaking --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 491d676df..52ca2c865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - rel="me" was missing its cache ### Removed -- Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) +- BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) ## 2.5.1 From 83c7415803774a4c56c718f5ebed2b9714ffdfcf Mon Sep 17 00:00:00 2001 From: kPherox Date: Wed, 15 Mar 2023 23:55:24 +0900 Subject: [PATCH 0121/1008] fix: append field values to bio before parsing --- lib/pleroma/web/metadata/providers/rel_me.ex | 14 +++++--------- .../pleroma/web/metadata/providers/rel_me_test.exs | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/web/metadata/providers/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex index e1890a050..eabd8cb00 100644 --- a/lib/pleroma/web/metadata/providers/rel_me.ex +++ b/lib/pleroma/web/metadata/providers/rel_me.ex @@ -9,8 +9,9 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do @impl Provider def build_tags(%{user: user}) do profile_tree = - Floki.parse_fragment!(user.bio) - |> prepend_fields_tag(user.fields) + user.bio + |> append_fields_tag(user.fields) + |> Floki.parse_fragment!() (Floki.attribute(profile_tree, "link[rel~=me]", "href") ++ Floki.attribute(profile_tree, "a[rel~=me]", "href")) @@ -19,13 +20,8 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do end) end - defp prepend_fields_tag(bio_tree, fields) do + defp append_fields_tag(bio, fields) do fields - |> Enum.reduce(bio_tree, fn %{"value" => v}, tree -> - case Floki.parse_fragment(v) do - {:ok, [a | _]} -> [a | tree] - _ -> tree - end - end) + |> Enum.reduce(bio, fn %{"value" => v}, res -> res <> v end) end end diff --git a/test/pleroma/web/metadata/providers/rel_me_test.exs b/test/pleroma/web/metadata/providers/rel_me_test.exs index b1177edb7..793669037 100644 --- a/test/pleroma/web/metadata/providers/rel_me_test.exs +++ b/test/pleroma/web/metadata/providers/rel_me_test.exs @@ -27,8 +27,8 @@ defmodule Pleroma.Web.Metadata.Providers.RelMeTest do assert RelMe.build_tags(%{user: user}) == [ {:link, [rel: "me", href: "http://some3.com"], []}, - {:link, [rel: "me", href: "http://profile.com"], []}, - {:link, [rel: "me", href: "https://another-link.com"], []} + {:link, [rel: "me", href: "https://another-link.com"], []}, + {:link, [rel: "me", href: "http://profile.com"], []} ] end end From 9a2523a09a2d9ccf5db12ef648444c51a98ab113 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 6 Mar 2023 21:16:34 +0100 Subject: [PATCH 0122/1008] instances: Store some metadata based on NodeInfo --- lib/pleroma/instances/instance.ex | 100 +++++++++++++++++- .../20230306112859_instances_add_metadata.exs | 14 +++ test/fixtures/mastodon-nodeinfo20.json | 1 + .../mastodon-well-known-nodeinfo.json | 1 + test/fixtures/wildebeest-nodeinfo21.json | 1 + .../wildebeest-well-known-nodeinfo.json | 1 + test/pleroma/instances/instance_test.exs | 60 +++++++++++ 7 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 priv/repo/migrations/20230306112859_instances_add_metadata.exs create mode 100644 test/fixtures/mastodon-nodeinfo20.json create mode 100644 test/fixtures/mastodon-well-known-nodeinfo.json create mode 100644 test/fixtures/wildebeest-nodeinfo21.json create mode 100644 test/fixtures/wildebeest-well-known-nodeinfo.json diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index a5529ad44..9756c66dc 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Instances.Instance do alias Pleroma.Instances alias Pleroma.Instances.Instance + alias Pleroma.Maps alias Pleroma.Repo alias Pleroma.User alias Pleroma.Workers.BackgroundWorker @@ -24,6 +25,14 @@ defmodule Pleroma.Instances.Instance do field(:favicon, :string) field(:favicon_updated_at, :naive_datetime) + embeds_one :metadata, Pleroma.Instances.Metadata, primary_key: false do + field(:software_name, :string) + field(:software_version, :string) + field(:software_repository, :string) + end + + field(:metadata_updated_at, :utc_datetime) + timestamps() end @@ -31,11 +40,17 @@ defmodule Pleroma.Instances.Instance do def changeset(struct, params \\ %{}) do struct - |> cast(params, [:host, :unreachable_since, :favicon, :favicon_updated_at]) + |> cast(params, __schema__(:fields) -- [:metadata]) + |> cast_embed(:metadata, with: &metadata_changeset/2) |> validate_required([:host]) |> unique_constraint(:host) end + def metadata_changeset(struct, params \\ %{}) do + struct + |> cast(params, [:software_name, :software_version, :software_repository]) + end + def filter_reachable([]), do: %{} def filter_reachable(urls_or_hosts) when is_list(urls_or_hosts) do @@ -198,6 +213,89 @@ defmodule Pleroma.Instances.Instance do end end + def get_or_update_metadata(%URI{host: host} = instance_uri) do + existing_record = Repo.get_by(Instance, %{host: host}) + now = NaiveDateTime.utc_now() + + if existing_record && existing_record.metadata_updated_at && + NaiveDateTime.diff(now, existing_record.metadata_updated_at) < 86_400 do + existing_record.metadata + else + metadata = scrape_metadata(instance_uri) + + if existing_record do + existing_record + |> changeset(%{metadata: metadata, metadata_updated_at: now}) + |> Repo.update() + else + %Instance{} + |> changeset(%{host: host, metadata: metadata, metadata_updated_at: now}) + |> Repo.insert() + end + + metadata + end + end + + defp get_nodeinfo_uri(well_known) do + links = Map.get(well_known, "links", []) + + nodeinfo21 = + Enum.find(links, &(&1["rel"] == "http://nodeinfo.diaspora.software/ns/schema/2.1"))["href"] + + nodeinfo20 = + Enum.find(links, &(&1["rel"] == "http://nodeinfo.diaspora.software/ns/schema/2.0"))["href"] + + cond do + is_binary(nodeinfo21) -> {:ok, nodeinfo21} + is_binary(nodeinfo20) -> {:ok, nodeinfo20} + true -> {:error, :no_links} + end + end + + defp scrape_metadata(%URI{} = instance_uri) do + try do + with {_, true} <- {:reachable, reachable?(instance_uri.host)}, + {:ok, %Tesla.Env{body: well_known_body}} <- + instance_uri + |> URI.merge("/.well-known/nodeinfo") + |> to_string() + |> Pleroma.HTTP.get([{"accept", "application/json"}]), + {:ok, well_known_json} <- Jason.decode(well_known_body), + {:ok, nodeinfo_uri} <- get_nodeinfo_uri(well_known_json), + {:ok, %Tesla.Env{body: nodeinfo_body}} <- + Pleroma.HTTP.get(nodeinfo_uri, [{"accept", "application/json"}]), + {:ok, nodeinfo} <- Jason.decode(nodeinfo_body) do + # Can extract more metadata from NodeInfo but need to be careful about it's size, + # can't just dump the entire thing + software = Map.get(nodeinfo, "software", %{}) + + %{ + software_name: software["name"], + software_version: software["version"] + } + |> Maps.put_if_present(:software_repository, software["repository"]) + else + {:reachable, false} -> + Logger.debug( + "Instance.scrape_metadata(\"#{to_string(instance_uri)}\") ignored unreachable host" + ) + + nil + + _ -> + nil + end + rescue + e -> + Logger.warn( + "Instance.scrape_metadata(\"#{to_string(instance_uri)}\") error: #{inspect(e)}" + ) + + nil + end + end + @doc """ Deletes all users from an instance in a background task, thus also deleting all of those users' activities and notifications. diff --git a/priv/repo/migrations/20230306112859_instances_add_metadata.exs b/priv/repo/migrations/20230306112859_instances_add_metadata.exs new file mode 100644 index 000000000..898f5220e --- /dev/null +++ b/priv/repo/migrations/20230306112859_instances_add_metadata.exs @@ -0,0 +1,14 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.InstancesAddMetadata do + use Ecto.Migration + + def change do + alter table(:instances) do + add(:metadata, :map) + add(:metadata_updated_at, :utc_datetime) + end + end +end diff --git a/test/fixtures/mastodon-nodeinfo20.json b/test/fixtures/mastodon-nodeinfo20.json new file mode 100644 index 000000000..35010fdf0 --- /dev/null +++ b/test/fixtures/mastodon-nodeinfo20.json @@ -0,0 +1 @@ +{"version":"2.0","software":{"name":"mastodon","version":"4.1.0"},"protocols":["activitypub"],"services":{"outbound":[],"inbound":[]},"usage":{"users":{"total":971090,"activeMonth":167218,"activeHalfyear":384808},"localPosts":52071541},"openRegistrations":true,"metadata":{}} \ No newline at end of file diff --git a/test/fixtures/mastodon-well-known-nodeinfo.json b/test/fixtures/mastodon-well-known-nodeinfo.json new file mode 100644 index 000000000..237d5462a --- /dev/null +++ b/test/fixtures/mastodon-well-known-nodeinfo.json @@ -0,0 +1 @@ +{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://mastodon.example.org/nodeinfo/2.0"}]} \ No newline at end of file diff --git a/test/fixtures/wildebeest-nodeinfo21.json b/test/fixtures/wildebeest-nodeinfo21.json new file mode 100644 index 000000000..c6af474bf --- /dev/null +++ b/test/fixtures/wildebeest-nodeinfo21.json @@ -0,0 +1 @@ +{"version":"2.1","software":{"name":"wildebeest","version":"0.0.1","repository":"https://github.com/cloudflare/wildebeest"},"protocols":["activitypub"],"usage":{"users":{"total":1,"activeMonth":1,"activeHalfyear":1}},"openRegistrations":false,"metadata":{"upstream":{"name":"mastodon","version":"3.5.1"}}} \ No newline at end of file diff --git a/test/fixtures/wildebeest-well-known-nodeinfo.json b/test/fixtures/wildebeest-well-known-nodeinfo.json new file mode 100644 index 000000000..c7ddb43af --- /dev/null +++ b/test/fixtures/wildebeest-well-known-nodeinfo.json @@ -0,0 +1 @@ +{"links":[{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.0","href":"https://wildebeest.example.org/nodeinfo/2.0"},{"rel":"http://nodeinfo.diaspora.software/ns/schema/2.1","href":"https://wildebeest.example.org/nodeinfo/2.1"}]} \ No newline at end of file diff --git a/test/pleroma/instances/instance_test.exs b/test/pleroma/instances/instance_test.exs index 861519bce..a769f9362 100644 --- a/test/pleroma/instances/instance_test.exs +++ b/test/pleroma/instances/instance_test.exs @@ -161,6 +161,66 @@ defmodule Pleroma.Instances.InstanceTest do end end + describe "get_or_update_metadata/1" do + test "Scrapes Wildebeest NodeInfo" do + Tesla.Mock.mock(fn + %{url: "https://wildebeest.example.org/.well-known/nodeinfo"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/wildebeest-well-known-nodeinfo.json") + } + + %{url: "https://wildebeest.example.org/nodeinfo/2.1"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/wildebeest-nodeinfo21.json") + } + end) + + expected = %{ + software_name: "wildebeest", + software_repository: "https://github.com/cloudflare/wildebeest", + software_version: "0.0.1" + } + + assert expected == + Instance.get_or_update_metadata(URI.parse("https://wildebeest.example.org/")) + + expected = %Pleroma.Instances.Instance.Pleroma.Instances.Metadata{ + software_name: "wildebeest", + software_repository: "https://github.com/cloudflare/wildebeest", + software_version: "0.0.1" + } + + assert expected == + Repo.get_by(Pleroma.Instances.Instance, %{host: "wildebeest.example.org"}).metadata + end + + test "Scrapes Mastodon NodeInfo" do + Tesla.Mock.mock(fn + %{url: "https://mastodon.example.org/.well-known/nodeinfo"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/mastodon-well-known-nodeinfo.json") + } + + %{url: "https://mastodon.example.org/nodeinfo/2.0"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/mastodon-nodeinfo20.json") + } + end) + + expected = %{ + software_name: "mastodon", + software_version: "4.1.0" + } + + assert expected == + Instance.get_or_update_metadata(URI.parse("https://mastodon.example.org/")) + end + end + test "delete_users_and_activities/1 deletes remote instance users and activities" do [mario, luigi, _peach, wario] = users = [ From f463b7570e902ce0b87f8e103c04ba2854717f3a Mon Sep 17 00:00:00 2001 From: anemone Date: Thu, 23 Mar 2023 23:14:52 -0700 Subject: [PATCH 0123/1008] Set background worker timeout to 15 minutes --- lib/pleroma/workers/background_worker.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 3805293bc..794417612 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -45,5 +45,5 @@ defmodule Pleroma.Workers.BackgroundWorker do end @impl Oban.Worker - def timeout(_job), do: :timer.seconds(5) + def timeout(_job), do: :timer.seconds(900) end From 10930f7507412fb1bea760723ee466774f95c6f3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 25 Mar 2023 23:20:07 -0400 Subject: [PATCH 0124/1008] Dedupe poll options --- lib/pleroma/web/common_api/utils.ex | 15 ++++++++--- .../controllers/status_controller_test.exs | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index ff0814329..a93c97e1e 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -145,6 +145,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do when is_list(options) do limits = Config.get([:instance, :poll_limits]) + options = options |> Enum.uniq() + with :ok <- validate_poll_expiration(expires_in, limits), :ok <- validate_poll_options_amount(options, limits), :ok <- validate_poll_options_length(options, limits) do @@ -180,10 +182,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do end defp validate_poll_options_amount(options, %{max_options: max_options}) do - if Enum.count(options) > max_options do - {:error, "Poll can't contain more than #{max_options} options"} - else - :ok + cond do + Enum.count(options) < 2 -> + {:error, "Poll must contain at least 2 options"} + + Enum.count(options) > max_options -> + {:error, "Poll can't contain more than #{max_options} options"} + + true -> + :ok end end diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 5bae2cd00..61d87ffbe 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -724,6 +724,32 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert object.data["type"] == "Question" assert length(object.data["oneOf"]) == 3 end + + test "cannot have only one option", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "desu~", + "poll" => %{"options" => ["mew"], "expires_in" => 1} + }) + + %{"error" => error} = json_response_and_validate_schema(conn, 422) + assert error == "Poll must contain at least 2 options" + end + + test "cannot have only duplicated options", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "desu~", + "poll" => %{"options" => ["mew", "mew"], "expires_in" => 1} + }) + + %{"error" => error} = json_response_and_validate_schema(conn, 422) + assert error == "Poll must contain at least 2 options" + end end test "get a status" do From 67d1897c6e9516ea76dff1456f15c9427813bda4 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 26 Mar 2023 11:11:26 -0400 Subject: [PATCH 0125/1008] Fix existing tests --- .../mastodon_api/controllers/status_controller_test.exs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 61d87ffbe..1e8979127 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -626,7 +626,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> put_req_header("content-type", "application/json") |> post("/api/v1/statuses", %{ "status" => "desu~", - "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} + "poll" => %{ + "options" => Enum.map(0..limit, fn num -> "desu #{num}" end), + "expires_in" => 1 + } }) %{"error" => error} = json_response_and_validate_schema(conn, 422) @@ -642,7 +645,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> post("/api/v1/statuses", %{ "status" => "...", "poll" => %{ - "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], + "options" => [String.duplicate(".", limit + 1), "lol"], "expires_in" => 1 } }) From c5d946bc92fa77f457b07e4fb9d113ff15cf87de Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 26 Mar 2023 15:12:40 -0400 Subject: [PATCH 0126/1008] Fix emoji reactions for legacy 2-tuple formats --- lib/pleroma/object.ex | 26 +++++++++++++ lib/pleroma/web/activity_pub/utils.ex | 6 +-- .../web/mastodon_api/views/status_view.ex | 4 +- .../controllers/emoji_reaction_controller.ex | 7 +--- test/pleroma/object_test.exs | 38 +++++++++++++++++++ test/pleroma/web/activity_pub/utils_test.exs | 27 ++++++++++++- .../mastodon_api/views/status_view_test.exs | 21 ++++++++++ .../emoji_reaction_controller_test.exs | 32 ++++++++++++++++ 8 files changed, 147 insertions(+), 14 deletions(-) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 38accae5d..aa137d250 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -425,4 +425,30 @@ defmodule Pleroma.Object do end def object_data_hashtags(_), do: [] + + def get_emoji_reactions(object) do + reactions = object.data["reactions"] + + if is_list(reactions) or is_map(reactions) do + reactions + |> Enum.map(fn + [_emoji, users, _maybe_url] = item when is_list(users) -> + item + + [emoji, users] when is_list(users) -> + [emoji, users, nil] + + # This case is here to process the Map situation, which will happen + # only with the legacy two-value format. + {emoji, users} when is_list(users) -> + [emoji, users, nil] + + _ -> + nil + end) + |> Enum.reject(&is_nil/1) + else + [] + end + end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 448cd1829..437220077 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -408,11 +408,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do end def get_cached_emoji_reactions(object) do - if is_list(object.data["reactions"]) do - object.data["reactions"] - else - [] - end + Object.get_emoji_reactions(object) end @spec add_like_to_object(Activity.t(), Object.t()) :: diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 95efd9fd0..dea22f9c2 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -334,8 +334,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end emoji_reactions = - object.data - |> Map.get("reactions", []) + object + |> Object.get_emoji_reactions() |> EmojiReactionController.filter_allowed_users( opts[:for], Map.get(opts, :with_muted, false) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex index e095fa04a..662cc15d6 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex @@ -28,8 +28,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do def index(%{assigns: %{user: user}} = conn, %{id: activity_id} = params) do with true <- Pleroma.Config.get([:instance, :show_reactions]), %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), - %Object{data: %{"reactions" => reactions}} when is_list(reactions) <- - Object.normalize(activity, fetch: false) do + %Object{} = object <- Object.normalize(activity, fetch: false), + reactions <- Object.get_emoji_reactions(object) do reactions = reactions |> filter(params) @@ -60,9 +60,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do reactions |> Stream.map(fn [emoji, users, url] when is_list(users) -> filter_emoji.(emoji, users, url) - {emoji, users, url} when is_list(users) -> filter_emoji.(emoji, users, url) - {emoji, users} when is_list(users) -> filter_emoji.(emoji, users, nil) - _ -> nil end) |> Stream.reject(&is_nil/1) end diff --git a/test/pleroma/object_test.exs b/test/pleroma/object_test.exs index d536e0b16..7bc5c9928 100644 --- a/test/pleroma/object_test.exs +++ b/test/pleroma/object_test.exs @@ -444,4 +444,42 @@ defmodule Pleroma.ObjectTest do Enum.sort_by(object.hashtags, & &1.name) end end + + describe "get_emoji_reactions/1" do + test "3-tuple current format" do + object = %Object{ + data: %{ + "reactions" => [ + ["x", ["https://some/user"], "https://some/emoji"] + ] + } + } + + assert Object.get_emoji_reactions(object) == object.data["reactions"] + end + + test "2-tuple legacy format" do + object = %Object{ + data: %{ + "reactions" => [ + ["x", ["https://some/user"]] + ] + } + } + + assert Object.get_emoji_reactions(object) == [["x", ["https://some/user"], nil]] + end + + test "Map format" do + object = %Object{ + data: %{ + "reactions" => %{ + "x" => ["https://some/user"] + } + } + } + + assert Object.get_emoji_reactions(object) == [["x", ["https://some/user"], nil]] + end + end end diff --git a/test/pleroma/web/activity_pub/utils_test.exs b/test/pleroma/web/activity_pub/utils_test.exs index e7d1e01c4..3f93c872b 100644 --- a/test/pleroma/web/activity_pub/utils_test.exs +++ b/test/pleroma/web/activity_pub/utils_test.exs @@ -587,15 +587,38 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do end describe "get_cached_emoji_reactions/1" do - test "returns the data or an emtpy list" do + test "returns the normalized data or an emtpy list" do object = insert(:note) assert Utils.get_cached_emoji_reactions(object) == [] object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]}) - assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"]]] + assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"], nil]] object = insert(:note, data: %{"reactions" => %{}}) assert Utils.get_cached_emoji_reactions(object) == [] end end + + describe "add_emoji_reaction_to_object/1" do + test "works with legacy 2-tuple format" do + user = insert(:user) + other_user = insert(:user) + third_user = insert(:user) + + note = + insert(:note, + user: user, + data: %{ + "reactions" => [["😿", [other_user.ap_id]]] + } + ) + + _activity = insert(:note_activity, user: user, note: note) + + Utils.add_emoji_reaction_to_object( + %Activity{data: %{"content" => "😿", "actor" => third_user.ap_id}}, + note + ) + end + end end diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index a1f3e3e1d..b93335190 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -74,6 +74,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do ] end + test "works with legacy-formatted reactions" do + user = insert(:user) + other_user = insert(:user) + + note = + insert(:note, + user: user, + data: %{ + "reactions" => [["😿", [other_user.ap_id]]] + } + ) + + activity = insert(:note_activity, user: user, note: note) + + status = StatusView.render("show.json", activity: activity, for: user) + + assert status[:pleroma][:emoji_reactions] == [ + %{name: "😿", count: 1, me: false, url: nil, account_ids: [other_user.id]} + ] + end + test "works correctly with badly formatted emojis" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "yo"}) diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs index d81b47a6b..21e7d4839 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs @@ -245,6 +245,38 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionControllerTest do result end + test "GET /api/v1/pleroma/statuses/:id/reactions with legacy format", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + note = + insert(:note, + user: user, + data: %{ + "reactions" => [["😿", [other_user.ap_id]]] + } + ) + + activity = insert(:note_activity, user: user, note: note) + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/reactions") + |> json_response_and_validate_schema(200) + + other_user_id = other_user.id + + assert [ + %{ + "name" => "😿", + "count" => 1, + "me" => false, + "url" => nil, + "accounts" => [%{"id" => ^other_user_id}] + } + ] = result + end + test "GET /api/v1/pleroma/statuses/:id/reactions?with_muted=true", %{conn: conn} do user = insert(:user) user2 = insert(:user) From d3b27d45a9115c04d4e469d1a9dc372ff8576d52 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 29 Mar 2023 23:18:44 +0300 Subject: [PATCH 0127/1008] List installed frontend refs in admin API --- docs/development/API/admin_api.md | 2 ++ .../web/admin_api/controllers/frontend_controller.ex | 12 +++++++++++- lib/pleroma/web/admin_api/views/frontend_view.ex | 3 ++- .../api_spec/operations/admin/frontend_operation.ex | 3 ++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md index f6e9f7d2a..7d31ee262 100644 --- a/docs/development/API/admin_api.md +++ b/docs/development/API/admin_api.md @@ -1585,6 +1585,7 @@ Returns the content of the document "build_url": "https://git.pleroma.social/pleroma/fedi-fe/-/jobs/artifacts/${ref}/download?job=build", "git": "https://git.pleroma.social/pleroma/fedi-fe", "installed": true, + "installed_refs": ["master"], "name": "fedi-fe", "ref": "master" }, @@ -1592,6 +1593,7 @@ Returns the content of the document "build_url": "https://git.pleroma.social/lambadalambda/kenoma/-/jobs/artifacts/${ref}/download?job=build", "git": "https://git.pleroma.social/lambadalambda/kenoma", "installed": false, + "installed_refs": [], "name": "kenoma", "ref": "master" } diff --git a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex index b4dbb82fe..145c1fa4a 100644 --- a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex @@ -22,7 +22,9 @@ defmodule Pleroma.Web.AdminAPI.FrontendController do [:frontends, :available] |> Config.get([]) |> Enum.map(fn {name, desc} -> - Map.put(desc, "installed", name in installed) + desc + |> Map.put("installed", name in installed) + |> Map.put("installed_refs", installed_refs(name)) end) render(conn, "index.json", frontends: frontends) @@ -43,4 +45,12 @@ defmodule Pleroma.Web.AdminAPI.FrontendController do [] end end + + def installed_refs(name) do + if name in installed() do + File.ls!(Path.join(Pleroma.Frontend.dir(), name)) + else + [] + end + end end diff --git a/lib/pleroma/web/admin_api/views/frontend_view.ex b/lib/pleroma/web/admin_api/views/frontend_view.ex index 0ca3d67cb..ae4016581 100644 --- a/lib/pleroma/web/admin_api/views/frontend_view.ex +++ b/lib/pleroma/web/admin_api/views/frontend_view.ex @@ -15,7 +15,8 @@ defmodule Pleroma.Web.AdminAPI.FrontendView do git: frontend["git"], build_url: frontend["build_url"], ref: frontend["ref"], - installed: frontend["installed"] + installed: frontend["installed"], + installed_refs: frontend["installed_refs"] } end end diff --git a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex index 4bfe5ac5a..89b1c474c 100644 --- a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex @@ -52,7 +52,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do git: %Schema{type: :string, format: :uri, nullable: true}, build_url: %Schema{type: :string, format: :uri, nullable: true}, ref: %Schema{type: :string}, - installed: %Schema{type: :boolean} + installed: %Schema{type: :boolean}, + installed_refs: %Schema{type: :array, items: %Schema{type: :string}} } } } From 3037d2780c735846bc82994f620c970b104362a7 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Thu, 30 Mar 2023 11:16:40 +0300 Subject: [PATCH 0128/1008] Also list frontends that are not in the config file --- .../admin_api/controllers/frontend_controller.ex | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex index 145c1fa4a..9e2ed4aac 100644 --- a/lib/pleroma/web/admin_api/controllers/frontend_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/frontend_controller.ex @@ -18,15 +18,24 @@ defmodule Pleroma.Web.AdminAPI.FrontendController do def index(conn, _params) do installed = installed() + # FIrst get frontends from config, + # then add frontends that are installed but not in the config frontends = - [:frontends, :available] - |> Config.get([]) + Config.get([:frontends, :available], []) |> Enum.map(fn {name, desc} -> desc |> Map.put("installed", name in installed) |> Map.put("installed_refs", installed_refs(name)) end) + frontends = + frontends ++ + (installed + |> Enum.filter(fn n -> not Enum.any?(frontends, fn f -> f["name"] == n end) end) + |> Enum.map(fn name -> + %{"name" => name, "installed" => true, "installed_refs" => installed_refs(name)} + end)) + render(conn, "index.json", frontends: frontends) end From 22b72cd6b87b4a684bde9c34741e5bb1e1204ed0 Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Sun, 5 Mar 2023 06:37:59 +0000 Subject: [PATCH 0129/1008] Merge branch 'tusooa/oban-common-pipeline' into 'develop' Stop oban from retrying if validating errors occur when processing incoming data See merge request pleroma/pleroma!3844 --- lib/pleroma/workers/receiver_worker.ex | 3 ++ test/pleroma/web/federator_test.exs | 2 +- test/pleroma/workers/receiver_worker_test.exs | 29 ++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index 4f513b907..cf1bb62b4 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -13,6 +13,9 @@ defmodule Pleroma.Workers.ReceiverWorker do {:ok, res} else {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} + {:error, :already_present} -> {:cancel, :already_present} + {:error, {:validate_object, reason}} -> {:cancel, reason} + {:error, {:error, {:validate, reason}}} -> {:cancel, reason} {:error, {:reject, reason}} -> {:cancel, reason} e -> e end diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs index 41d1c5d5e..1ffe6aae1 100644 --- a/test/pleroma/web/federator_test.exs +++ b/test/pleroma/web/federator_test.exs @@ -133,7 +133,7 @@ defmodule Pleroma.Web.FederatorTest do assert {:ok, _activity} = ObanHelpers.perform(job) assert {:ok, job} = Federator.incoming_ap_doc(params) - assert {:error, :already_present} = ObanHelpers.perform(job) + assert {:cancel, :already_present} = ObanHelpers.perform(job) end test "rejects incoming AP docs with incorrect origin" do diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 283beee4d..acea0ae00 100644 --- a/test/pleroma/workers/receiver_worker_test.exs +++ b/test/pleroma/workers/receiver_worker_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do alias Pleroma.Workers.ReceiverWorker - test "it ignores MRF reject" do + test "it does not retry MRF reject" do params = insert(:note).data with_mock Pleroma.Web.ActivityPub.Transmogrifier, @@ -22,4 +22,31 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do }) end end + + test "it does not retry ObjectValidator reject" do + params = + insert(:note_activity).data + |> Map.put("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id()) + |> Map.put("object", %{ + "type" => "Note", + "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id() + }) + + with_mock Pleroma.Web.ActivityPub.ObjectValidator, [:passthrough], + validate: fn _, _ -> {:error, %Ecto.Changeset{}} end do + assert {:cancel, {:error, %Ecto.Changeset{}}} = + ReceiverWorker.perform(%Oban.Job{ + args: %{"op" => "incoming_ap_doc", "params" => params} + }) + end + end + + test "it does not retry duplicates" do + params = insert(:note_activity).data + + assert {:cancel, :already_present} = + ReceiverWorker.perform(%Oban.Job{ + args: %{"op" => "incoming_ap_doc", "params" => params} + }) + end end From d640df39276e19c1adc31d46692e46b7cdcfb991 Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Mon, 6 Mar 2023 21:08:27 +0000 Subject: [PATCH 0130/1008] Merge branch 'fix/static-fe-feed-500' into 'develop' fix: remove static_fe pipeline for /users/:nickname/feed See merge request pleroma/pleroma!3852 --- lib/pleroma/web/router.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ba1d64ab2..c1a690e28 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -835,8 +835,7 @@ defmodule Pleroma.Web.Router do end scope "/", Pleroma.Web do - # Note: html format is supported only if static FE is enabled - pipe_through([:accepts_html_xml, :static_fe]) + pipe_through([:accepts_html_xml]) get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed) end From 937df7e46536fee9703285e8a136bba67f1f907b Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Mon, 6 Mar 2023 22:55:24 +0000 Subject: [PATCH 0131/1008] Merge branch 'fix/tag-feed-crashes' into 'develop' fix: atom/rss feed issues Closes #3045 See merge request pleroma/pleroma!3851 --- lib/pleroma/web/feed/feed_view.ex | 10 ++--- lib/pleroma/web/metadata/utils.ex | 5 ++- .../templates/feed/feed/_activity.atom.eex | 4 +- .../web/templates/feed/feed/_activity.rss.eex | 2 +- .../feed/feed/_tag_activity.atom.eex | 4 +- .../templates/feed/feed/_tag_activity.xml.eex | 2 +- .../pleroma/web/feed/user_controller_test.exs | 44 +++++++++++++++++-- test/pleroma/web/metadata/utils_test.exs | 2 +- 8 files changed, 56 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 449659f4b..034722eb2 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.Feed.FeedView do use Phoenix.HTML use Pleroma.Web, :view - alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.Gettext @@ -72,7 +71,9 @@ defmodule Pleroma.Web.Feed.FeedView do def last_activity(activities), do: List.last(activities) - def activity_title(%{"content" => content, "summary" => summary} = data, opts \\ %{}) do + def activity_title(%{"content" => content} = data, opts \\ %{}) do + summary = Map.get(data, "summary", "") + title = cond do summary != "" -> summary @@ -81,9 +82,8 @@ defmodule Pleroma.Web.Feed.FeedView do end title - |> Pleroma.Web.Metadata.Utils.scrub_html() - |> Pleroma.Emoji.Formatter.demojify() - |> Formatter.truncate(opts[:max_length], opts[:omission]) + |> Pleroma.Web.Metadata.Utils.scrub_html_and_truncate(opts[:max_length], opts[:omission]) + |> HtmlEntities.encode() end def activity_description(data) do diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 15414a988..80a8be9a2 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -30,12 +30,13 @@ defmodule Pleroma.Web.Metadata.Utils do |> scrub_html_and_truncate_object_field(object) end - def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do + def scrub_html_and_truncate(content, max_length \\ 200, omission \\ "...") + when is_binary(content) do content |> scrub_html |> Emoji.Formatter.demojify() |> HtmlEntities.decode() - |> Formatter.truncate(max_length) + |> Formatter.truncate(max_length, omission) end def scrub_html(content) when is_binary(content) do diff --git a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex index 260338772..b774f7984 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.atom.eex @@ -4,8 +4,8 @@ <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> <%= activity_description(@data) %> - <%= to_rfc3339(@activity.data["published"]) %> - <%= to_rfc3339(@activity.data["published"]) %> + <%= to_rfc3339(@data["published"]) %> + <%= to_rfc3339(@data["published"]) %> <%= activity_context(@activity) %> diff --git a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex index 5c8f35fe4..7de98f736 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.rss.eex @@ -4,7 +4,7 @@ <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> <%= activity_description(@data) %> - <%= to_rfc2822(@activity.data["published"]) %> + <%= to_rfc2822(@data["published"]) %> <%= activity_context(@activity) %> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex index 25980c1e4..03c222975 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex @@ -7,8 +7,8 @@ <%= @data["id"] %> <%= activity_title(@data, Keyword.get(@feed_config, :post_title, %{})) %> <%= activity_description(@data) %> - <%= to_rfc3339(@activity.data["published"]) %> - <%= to_rfc3339(@activity.data["published"]) %> + <%= to_rfc3339(@data["published"]) %> + <%= to_rfc3339(@data["published"]) %> <%= activity_context(@activity) %> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex index d582c83e8..1b8c34b87 100644 --- a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex @@ -4,7 +4,7 @@ <%= activity_context(@activity) %> <%= activity_context(@activity) %> - <%= to_rfc2822(@activity.data["published"]) %> + <%= to_rfc2822(@data["published"]) %> <%= activity_description(@data) %> <%= for attachment <- @data["attachment"] || [] do %> diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs index de32d3d4b..d3c4108de 100644 --- a/test/pleroma/web/feed/user_controller_test.exs +++ b/test/pleroma/web/feed/user_controller_test.exs @@ -57,9 +57,23 @@ defmodule Pleroma.Web.Feed.UserControllerTest do ) note_activity2 = insert(:note_activity, note: note2) + + note3 = + insert(:note, + user: user, + data: %{ + "content" => "This note tests whether HTML entities are truncated properly", + "summary" => "Won't, didn't fail", + "inReplyTo" => note_activity2.id + } + ) + + _note_activity3 = insert(:note_activity, note: note3) object = Object.normalize(note_activity, fetch: false) - [user: user, object: object, max_id: note_activity2.id] + encoded_title = FeedView.activity_title(note3.data) + + [user: user, object: object, max_id: note_activity2.id, encoded_title: encoded_title] end test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_id} do @@ -74,7 +88,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> SweetXml.parse() |> SweetXml.xpath(~x"//entry/title/text()"l) - assert activity_titles == ['2hu', '2hu & as'] + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -105,7 +119,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> SweetXml.parse() |> SweetXml.xpath(~x"//item/title/text()"l) - assert activity_titles == ['2hu', '2hu & as'] + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] assert resp =~ FeedView.escape(object.data["content"]) assert resp =~ FeedView.escape(object.data["summary"]) assert resp =~ FeedView.escape(object.data["context"]) @@ -176,6 +190,30 @@ defmodule Pleroma.Web.Feed.UserControllerTest do |> get("/users/#{user.nickname}/feed.rss") |> response(200) end + + test "does not mangle HTML entities midway", %{ + conn: conn, + user: user, + object: object, + encoded_title: encoded_title + } do + resp = + conn + |> put_req_header("accept", "application/atom+xml") + |> get(user_feed_path(conn, :feed, user.nickname)) + |> response(200) + + activity_titles = + resp + |> SweetXml.parse() + |> SweetXml.xpath(~x"//entry/title/text()"l) + + assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as'] + assert resp =~ FeedView.escape(object.data["content"]) + assert resp =~ FeedView.escape(object.data["summary"]) + assert resp =~ FeedView.escape(object.data["context"]) + assert resp =~ encoded_title + end end # Note: see ActivityPubControllerTest for JSON format tests diff --git a/test/pleroma/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs index 85ef6033a..3daf852fb 100644 --- a/test/pleroma/web/metadata/utils_test.exs +++ b/test/pleroma/web/metadata/utils_test.exs @@ -72,7 +72,7 @@ defmodule Pleroma.Web.Metadata.UtilsTest do end end - describe "scrub_html_and_truncate/2" do + describe "scrub_html_and_truncate/3" do test "it returns text without encode HTML" do assert Utils.scrub_html_and_truncate("Pleroma's really cool!") == "Pleroma's really cool!" end From 40f14fd31cb1a92b7f1b2a533a725c709fefbb3b Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 12 Mar 2023 16:06:00 +0000 Subject: [PATCH 0132/1008] Merge branch 'remove-crypt' into 'develop' Remove crypt(3) support Closes #3030 and #3062 See merge request pleroma/pleroma!3847 --- CHANGELOG.md | 1 + lib/pleroma/web/plugs/authentication_plug.ex | 8 ----- mix.exs | 1 - mix.lock | 1 - .../web/plugs/authentication_plug_test.exs | 30 ------------------- 5 files changed, 1 insertion(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a7ec1032..1742ee2f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed ### Removed +- BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) ## 2.5.1 diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index a7fd697b5..f912a1542 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -38,10 +38,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do def call(conn, _), do: conn - def checkpw(password, "$6" <> _ = password_hash) do - :crypt.crypt(password, password_hash) == password_hash - end - def checkpw(password, "$2" <> _ = password_hash) do # Handle bcrypt passwords for Mastodon migration Bcrypt.verify_pass(password, password_hash) @@ -60,10 +56,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do do_update_password(user, password) end - def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do - do_update_password(user, password) - end - def maybe_update_password(user, _), do: {:ok, user} defp do_update_password(user, password) do diff --git a/mix.exs b/mix.exs index ab0be4deb..7439bbca6 100644 --- a/mix.exs +++ b/mix.exs @@ -150,7 +150,6 @@ defmodule Pleroma.Mixfile do {:sweet_xml, "~> 0.7.2"}, {:earmark, "~> 1.4.22"}, {:bbcode_pleroma, "~> 0.2.0"}, - {:crypt, "~> 1.0"}, {:cors_plug, "~> 2.0"}, {:web_push_encryption, "~> 0.3.1"}, {:swoosh, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 302786326..8419dc739 100644 --- a/mix.lock +++ b/mix.lock @@ -21,7 +21,6 @@ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, "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": {:hex, :crypt, "1.0.1", "a3567e1c651a2ec42c6650d9f3ab789e0f12a508c060653a9bbb5fafe60f043c", [:rebar3], [], "hexpm", "968dffe321c7a5d9f9b4577c4a4ff56a1c26d1a8a2270eb22c7636a0b43d3982"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs index 41fdb93bc..b8acd01c5 100644 --- a/test/pleroma/web/plugs/authentication_plug_test.exs +++ b/test/pleroma/web/plugs/authentication_plug_test.exs @@ -70,28 +70,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do assert "$pbkdf2" <> _ = user.password_hash end - @tag :skip_on_mac - test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do - user = - insert(:user, - password_hash: - "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" - ) - - conn = - conn - |> assign(:auth_user, user) - |> assign(:auth_credentials, %{password: "password"}) - |> AuthenticationPlug.call(%{}) - - assert conn.assigns.user.id == conn.assigns.auth_user.id - assert conn.assigns.token == nil - assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) - - user = User.get_by_id(user.id) - assert "$pbkdf2" <> _ = user.password_hash - end - describe "checkpw/2" do test "check pbkdf2 hash" do hash = @@ -101,14 +79,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do refute AuthenticationPlug.checkpw("test-password1", hash) end - @tag :skip_on_mac - test "check sha512-crypt hash" do - hash = - "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" - - assert AuthenticationPlug.checkpw("password", hash) - end - test "check bcrypt hash" do hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS" From ad38cc3b0c7895554291e874dfcfcf011b21f886 Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 16 Mar 2023 08:11:44 +0000 Subject: [PATCH 0133/1008] Merge branch 'docs-otp-support' into 'develop' docs: Be more explicit about the level of compatibility of OTP releases See merge request pleroma/pleroma!3849 --- docs/installation/otp_en.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index 8c02201e6..f2812346b 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -2,15 +2,16 @@ {! backend/installation/otp_vs_from_source.include !} -This guide covers a installation using an OTP release. To install Pleroma from source, please check out the corresponding guide for your distro. +This guide covers a installation using OTP releases as built by the Pleroma project, it is meant as a fallback to distribution packages/recipes which are the preferred installation method. +To install Pleroma from source, please check out the corresponding guide for your distro. ## Pre-requisites -* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below +* A machine you have root access to running Debian GNU/Linux or compatible (eg. Ubuntu), or Alpine on `x86_64`, `aarch64` or `armv7l` CPU. If you are not sure what you are running see [Detecting flavour section](#detecting-flavour) below * A (sub)domain pointed to the machine -You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo su`/`su`. +You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo -i`/`su`. -While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine. +Similarly to other binaries, OTP releases tend to be only compatible with the distro they are built on, as such this guide focuses only on Debian/Ubuntu and Alpine. ### Detecting flavour @@ -19,7 +20,7 @@ Paste the following into the shell: arch="$(uname -m)";if [ "$arch" = "x86_64" ];then arch="amd64";elif [ "$arch" = "armv7l" ];then arch="arm";elif [ "$arch" = "aarch64" ];then arch="arm64";else echo "Unsupported arch: $arch">&2;fi;if getconf GNU_LIBC_VERSION>/dev/null;then libc_postfix="";elif [ "$(ldd 2>&1|head -c 9)" = "musl libc" ];then libc_postfix="-musl";elif [ "$(find /lib/libc.musl*|wc -l)" ];then libc_postfix="-musl";else echo "Unsupported libc">&2;fi;echo "$arch$libc_postfix" ``` -If your platform is supported the output will contain the flavour string, you will need it later. If not, this just means that we don't build releases for your platform, you can still try installing from source. +This should give your flavour string. If not this just means that we don't build releases for your platform, you can still try installing from source. ### Installing the required packages From e4288df50266a73ae1485c684b18d6f20f3234b0 Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Sat, 25 Mar 2023 05:40:35 +0000 Subject: [PATCH 0134/1008] Merge branch 'background-timeout' into 'develop' Set background worker timeout to 15 minutes See merge request pleroma/pleroma!3857 --- lib/pleroma/workers/background_worker.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 3805293bc..794417612 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -45,5 +45,5 @@ defmodule Pleroma.Workers.BackgroundWorker do end @impl Oban.Worker - def timeout(_job), do: :timer.seconds(5) + def timeout(_job), do: :timer.seconds(900) end From 6a63dced4a567e0ba84c3c1e796eff93ba015255 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Thu, 30 Mar 2023 19:25:35 +0300 Subject: [PATCH 0135/1008] Fix tests for frontend installation --- lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex | 2 +- .../web/admin_api/controllers/frontend_controller_test.exs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex index 89b1c474c..3e85c44d2 100644 --- a/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/frontend_operation.ex @@ -51,7 +51,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.FrontendOperation do name: %Schema{type: :string}, git: %Schema{type: :string, format: :uri, nullable: true}, build_url: %Schema{type: :string, format: :uri, nullable: true}, - ref: %Schema{type: :string}, + ref: %Schema{type: :string, nullable: true}, installed: %Schema{type: :boolean}, installed_refs: %Schema{type: :array, items: %Schema{type: :string}} } diff --git a/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs b/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs index 38a23b224..0d1a4999e 100644 --- a/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs @@ -89,6 +89,7 @@ defmodule Pleroma.Web.AdminAPI.FrontendControllerTest do "build_url" => "http://gensokyo.2hu/builds/${ref}", "git" => nil, "installed" => true, + "installed_refs" => ["fantasy"], "name" => "pleroma", "ref" => "fantasy" } From 81e1d6d9d06225204530d06d549dbd17419a27bb Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Sun, 2 Apr 2023 01:02:22 +0900 Subject: [PATCH 0136/1008] Add Fedistar as a desktop client in docs --- docs/clients.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/clients.md b/docs/clients.md index 31d2d27c3..67ef7dd46 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -23,6 +23,13 @@ Feel free to contact us to be added to this list! - Platforms: Windows, Mac, Linux - Features: MastoAPI, Streaming Ready +### Fedistar +- Homepage: +- Source Code: +- Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto) +- Platforms: Windows, Mac, Linux +- Features: MastoAPI, Streaming Ready + ## Handheld ### AndStatus - Homepage: From 17f1b8d13355e40f0d136f9b80876c16c0b67175 Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Sun, 2 Apr 2023 01:03:15 +0900 Subject: [PATCH 0137/1008] Remove Roma from docs Because this app is no longer maintained --- docs/clients.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/clients.md b/docs/clients.md index 67ef7dd46..664955bb5 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -3,12 +3,6 @@ Note: Additional clients may be working but theses are officially supporting Ple Feel free to contact us to be added to this list! ## Desktop -### Roma for Desktop -- Homepage: -- Source Code: -- Platforms: Windows, Mac, Linux -- Features: MastoAPI, Streaming Ready - ### Social - Source Code: - Contact: [@brainblasted@social.libre.fi](https://social.libre.fi/users/brainblasted) From e5f36f9f8dfbcb718e64a2cd5b174a05b3e35f4d Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Sun, 2 Apr 2023 01:07:07 +0900 Subject: [PATCH 0138/1008] Update contact information for Whalebird and Fedistar --- docs/clients.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/clients.md b/docs/clients.md index 664955bb5..ad7eb7807 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -13,14 +13,14 @@ Feel free to contact us to be added to this list! ### Whalebird - Homepage: - Source Code: -- Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto) +- Contact: [@whalebird@pleroma.io](https://pleroma.io/users/whalebird) - Platforms: Windows, Mac, Linux - Features: MastoAPI, Streaming Ready ### Fedistar - Homepage: - Source Code: -- Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto) +- Contact: [@fedistar@pleroma.io](https://pleroma.io/users/fedistar) - Platforms: Windows, Mac, Linux - Features: MastoAPI, Streaming Ready From ce16ff7ae8bffa7a27aba4260840ccdac03d07b5 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 4 Apr 2023 10:56:19 +0200 Subject: [PATCH 0139/1008] mix: Update all dependencies --- mix.exs | 6 ++-- mix.lock | 90 +++++++++++++++++++++++++++++--------------------------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/mix.exs b/mix.exs index 0cc57d51c..027442e33 100644 --- a/mix.exs +++ b/mix.exs @@ -126,7 +126,8 @@ defmodule Pleroma.Mixfile do {:telemetry_poller, "~> 1.0"}, {:tzdata, "~> 1.0.3"}, {:plug_cowboy, "~> 2.3"}, - {:oban, "~> 2.13"}, + # oban 2.14 requires Elixir 1.12+ + {:oban, "~> 2.13.4"}, {:gettext, git: "https://github.com/tusooa/gettext.git", ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808", @@ -148,7 +149,8 @@ defmodule Pleroma.Mixfile do {:ex_aws, "~> 2.1.6"}, {:ex_aws_s3, "~> 2.0"}, {:sweet_xml, "~> 0.7.2"}, - {:earmark, "~> 1.4.22"}, + # earmark 1.4.23 requires Elixir 1.12+ + {:earmark, "1.4.22"}, {:bbcode_pleroma, "~> 0.2.0"}, {:cors_plug, "~> 2.0"}, {:web_push_encryption, "~> 0.3.1"}, diff --git a/mix.lock b/mix.lock index f2336cf87..0af2c4292 100644 --- a/mix.lock +++ b/mix.lock @@ -2,66 +2,66 @@ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, - "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.0", "6cb662d5c1b0a8858801cf20997bd006e7016aa8c52959c9ef80e0f34fb60b7a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2c81d61d4f6ed0e5cf7bf27a9109b791ff216a1034b3d541327484f46dd43769"}, - "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, + "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"}, + "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "cachex": {:hex, :cachex, "3.3.0", "6f2ebb8f27491fe39121bd207c78badc499214d76c695658b19d6079beeca5c2", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d90e5ee1dde14cef33f6b187af4335b88748b72b30c038969176cd4e6ccc31a1"}, + "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]}, - "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"}, + "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"}, + "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, "concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"}, "covertool": {:hex, :covertool, "2.0.4", "54acff6cddd88d28dea663cd2e1fe20dd32fcf5f5d3aff7d59031ce44ce39efa", [:rebar3], [], "hexpm", "5c9568ba4308fda2082172737c80c31d991ea83961eb10791f06106a870d0cdc"}, "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, - "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, + "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, + "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, - "db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"}, + "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.22", "ea3e45c6359446dc308be0a64ce82a03260d973de7d0625a762e6d352ff57958", [:mix], [{:earmark_parser, "~> 1.4.23", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "1caf5145665a42fd76d5317286b0c171861fb1c04f86ab103dde76868814fdfb"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"}, - "ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"}, + "ecto": {:hex, :ecto, "3.9.5", "9f0aa7ae44a1577b651c98791c6988cd1b69b21bc724e3fd67090b97f7604263", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d4f3115d8cbacdc0bfa4b742865459fb1371d0715515842a1fb17fe31920b74c"}, "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_psql_extras": {:hex, :ecto_psql_extras, "0.7.4", "5d43fd088d39a158c860b17e8d210669587f63ec89ea122a4654861c8c6e2db4", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.15.7", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "311db02f1b772e3d0dc7f56a05044b5e1499d78ed6abf38885e1ca70059449e5"}, - "ecto_sql": {:hex, :ecto_sql, "3.9.0", "2bb21210a2a13317e098a420a8c1cc58b0c3421ab8e3acfa96417dab7817918c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a8f3f720073b8b1ac4c978be25fa7960ed7fd44997420c304a4a2e200b596453"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"}, + "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, - "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"}, + "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"}, - "ex_aws_s3": {:hex, :ex_aws_s3, "2.2.0", "07a09de557070320e264893c0acc8a1d2e7ddf80155736e0aed966486d1988e6", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "15175c613371e29e1f88b78ec8a4327389ca1ec5b34489744b175727496b21bd"}, + "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"}, + "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "fast_html": {:hex, :fast_html, "2.0.5", "c61760340606c1077ff1f196f17834056cb1dd3d5cb92a9f2cabf28bc6221c3c", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "605f4f4829443c14127694ebabb681778712ceecb4470ec32aa31012330e6506"}, - "fast_sanitize": {:hex, :fast_sanitize, "0.2.2", "3cbbaebaea6043865dfb5b4ecb0f1af066ad410a51470e353714b10c42007b81", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "69f204db9250afa94a0d559d9110139850f57de2b081719fbafa1e9a89e94466"}, + "fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.10.2", "9ad27d68270d879f73f26604bb2e573d40f29bf0e907064a9a337f90a16a0312", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd8b11b282072cec2ef30852283949c248bd5d2820c88d8acc89402b81db7550"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.30.1", "75d35526d3a1459920b6e87fdbc2e0b8a3670f965dd0903708d2b267e0904c55", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e9c03524447d1c4cbfccd672d739b8c18453eee377846b119d4fd71b1a176bb8"}, + "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "gettext": {:git, "https://github.com/tusooa/gettext.git", "72fb2496b6c5280ed911bdc3756890e7f38a4808", [ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808"]}, - "gun": {:hex, :gun, "2.0.0-rc.2", "7c489a32dedccb77b6e82d1f3c5a7dadfbfa004ec14e322cdb5e579c438632d2", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6b9d1eae146410d727140dbf8b404b9631302ecc2066d1d12f22097ad7d254fc"}, + "gun": {:hex, :gun, "2.0.0", "2326bc0fd6d9cf628419708270d6fe8b02b8d002cf992e4165a77d997b1defd0", [:make, :rebar3], [{:cowlib, "2.12.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6613cb7c62930dc8d58263c44dda72f8556346ba88358fc929dcbc5f76d04569"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "http_signatures": {:hex, :http_signatures, "0.1.1", "ca7ebc1b61542b163644c8c3b1f0e0f41037d35f2395940d3c6c7deceab41fd8", [:mix], [], "hexpm", "cc3b8a007322cc7b624c0c15eec49ee58ac977254ff529a3c482f681465942a3"}, - "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, + "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, - "joken": {:hex, :joken, "2.3.0", "62a979c46f2c81dcb8ddc9150453b60d3757d1ac393c72bb20fc50a7b0827dc6", [:mix], [{:jose, "~> 1.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "57b263a79c0ec5d536ac02d569c01e6b4de91bd1cb825625fe90eab4feb7bc1e"}, - "jose": {:hex, :jose, "1.11.1", "59da64010c69aad6cde2f5b9248b896b84472e99bd18f246085b7b9fe435dcdb", [:mix, :rebar3], [], "hexpm", "078f6c9fb3cd2f4cfafc972c814261a7d1e8d2b3685c0a76eb87e158efff1ac5"}, + "joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"}, + "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"}, "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"}, @@ -72,51 +72,53 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, + "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"}, - "mogrify": {:hex, :mogrify, "0.9.1", "a26f107c4987477769f272bd0f7e3ac4b7b75b11ba597fd001b877beffa9c068", [:mix], [], "hexpm", "134edf189337d2125c0948bf0c228fdeef975c594317452d536224069a5b7f05"}, - "mox": {:hex, :mox, "1.0.0", "4b3c7005173f47ff30641ba044eb0fe67287743eec9bd9545e37f3002b0a9f8b", [:mix], [], "hexpm", "201b0a20b7abdaaab083e9cf97884950f8a30a1350a1da403b3145e213c6f4df"}, + "mogrify": {:hex, :mogrify, "0.9.2", "b360984adea7dd6a55f18028e6327973c58de7f548fdb86c9859848aa904d5b0", [:mix], [], "hexpm", "c18d10fd70ca20e2585301616c89f6e4f7159d92efc9cc8ee579e00c886f699d"}, + "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, "nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, - "oban": {:hex, :oban, "2.13.4", "b4c4f48f4c89cc01036670eefa28aa9c03d09aadd402655475b936983d597006", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d26f82b409e2d7928fbb75a17716e06ad3f783ebe9af260e3dd23abed7f124"}, - "open_api_spex": {:hex, :open_api_spex, "3.16.0", "9843af4e87550cd8ac5821b10e4c74f1d51f0d4e3310f824d780614743423b25", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "bb0be24a648b73e8fc8cbda17f514b8486262275e8b33e8b5ae66283df972129"}, + "oban": {:hex, :oban, "2.13.6", "a0cb1bce3bd393770512231fb5a3695fa19fd3af10d7575bf73f837aee7abf43", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c1c5eb16f377b3cbbf2ea14be24d20e3d91285af9d1ac86260b7c2af5464887"}, + "open_api_spex": {:hex, :open_api_spex, "3.16.1", "8137c338129d63060b4b04947c6c57429f86267045c479c703a38a6d3f98dee1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "ef6fd778ac121af866b48b75ad4ad256b6ff33949113ea4aa1629af8bfdfdbfb"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, - "phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, + "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, - "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, - "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.2", "0769470265eb13af01b5001b29cb935f4710d6adaa1ffc18417a570a337a2f0f", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5bc6c6b38a2ca8b5020b442322fcee6afd5e641637a0b1fb059d4bd89bc58e7b"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.5", "63f52a6f9f6983f04e424586ff897c016ecc5e4f8d1e2c22c2887af1c57215d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c5586e6a3d4df71b8214c769d4f5eb8ece2b4001711a7ca0f97323c36958b0e3"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, - "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.1.0", "f8e4780705c9f254cc853f7a40e25f7198ba4d91102bcfad2226669b69766b35", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "aa82f10afd9a4b6080fdf3274dbb9432b25b210d42b4b6b55308f6e59cd87c3d"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"}, - "phoenix_view": {:hex, :phoenix_view, "2.0.1", "a653e3d9d944aace0a064e4a13ad473ffa68f7bc4ca42dbf83cc1d464f1fb295", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "6c358e2cefc5f341c728914b867c556bbfd239fed9e881bac257d70cb2b8a6f6"}, + "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "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.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, - "pot": {:hex, :pot, "1.0.1", "81b511b1fa7c3123171c265cb7065a1528cebd7277b0cbc94257c50a8b2e4c17", [:rebar3], [], "hexpm", "ed87f5976531d91528452faa1138a5328db7f9f20d8feaae15f5051f79bcfb6d"}, + "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, "prom_ex": {:hex, :prom_ex, "1.7.1", "39331ee3fe6f9a8587d8208bf9274a253bb80281700e127dd18786cda5e08c37", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.10.2", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "4c978872b88a929833925a0f4d0561824804c671fdd04581e765509ed0a6ed08"}, - "prometheus": {:hex, :prometheus, "4.8.0", "1ce1e1002b173c336d61f186b56263346536e76814edd9a142e12aeb2d6c1ad2", [:mix, :rebar3], [], "hexpm", "0fc2e17103073edb3758a46a5d44b006191bf25b73cbaa2b779109de396afcb5"}, + "prometheus": {:hex, :prometheus, "4.10.0", "792adbf0130ff61b5fa8826f013772af24b6e57b984445c8d602c8a0355704a1", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "2a99bb6dce85e238c7236fde6b0064f9834dc420ddbd962aac4ea2a3c3d59384"}, "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": {:git, "https://github.com/lanodan/prometheus.ex.git", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]}, "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"}, "prometheus_phx": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/prometheus-phx.git", "9cd8f248c9381ffedc799905050abce194a97514", [branch: "no-logging"]}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm", "0273a6483ccb936d79ca19b0ab629aef0dba958697c94782bb728b920dfc6a79"}, + "quantile_estimator": {:hex, :quantile_estimator, "0.2.1", "ef50a361f11b5f26b5f16d0696e46a9e4661756492c981f7b2229ef42ff1cd15", [:rebar3], [], "hexpm", "282a8a323ca2a845c9e6f787d166348f776c1d4a41ede63046d72d422e3da946"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"}, + "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, - "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, + "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "sweet_xml": {:hex, :sweet_xml, "0.7.2", "4729f997286811fabdd8288f8474e0840a76573051062f066c4b597e76f14f9f", [:mix], [], "hexpm", "6894e68a120f454534d99045ea3325f7740ea71260bc315f82e29731d570a6e8"}, - "swoosh": {:hex, :swoosh, "1.8.2", "af9a22ab2c0d20b266f61acca737fa11a121902de9466a39e91bacdce012101c", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d058ba750eafadb6c09a84a352c14c5d1eeeda6e84945fcc95785b7f3067b7db"}, + "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"}, + "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, @@ -124,10 +126,10 @@ "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.0.2", "c98b1c580de637bfeac00db41b9fb91fb4c3548ee3d512a8ed7299172312eaf3", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "48351a0d56f80e38c997b44232b1043e0a081670d16766eee920e6254175b730"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, - "timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"}, + "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.0.5", "69f1ee029a49afa04ad77801febaf69385f3d3e3d1e4b56b9469025677b89a28", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "55519aa2a99e5d2095c1e61cc74c9be69688f8ab75c27da724eb8279ff402a5a"}, - "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"}, + "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, From 89a40b867df13205e710c136e5c6cd9be1b76733 Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Tue, 4 Apr 2023 16:20:46 +0000 Subject: [PATCH 0140/1008] Allow more than 1 changelog entry --- tools/check-changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/check-changelog b/tools/check-changelog index 970f3066f..c3e9e07c7 100644 --- a/tools/check-changelog +++ b/tools/check-changelog @@ -8,12 +8,12 @@ for i in add remove fix security skip; do retcode=$? if [ $retcode -eq 0 ]; then echo "found $CI_MERGE_REQUEST_IID.$i" + count=$(( count++ )) else echo "no $CI_MERGE_REQUEST_IID.$i" fi - count=$(( $count + 1 - $retcode )) done -if [ $count -eq 1 ]; then +if [ $count -gt 0 ]; then echo "ok" exit 0 else From 686c3e03bd6c13e732e95d2f81cc4bcd0acf015a Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 4 Apr 2023 12:24:45 -0400 Subject: [PATCH 0141/1008] Fix counting --- tools/check-changelog | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/check-changelog b/tools/check-changelog index c3e9e07c7..b94b52755 100644 --- a/tools/check-changelog +++ b/tools/check-changelog @@ -1,14 +1,14 @@ #!/bin/sh -echo $CI_MERGE_REQUEST_IID -ls changelog.d +echo "looking for change log of $CI_MERGE_REQUEST_IID" + count=0 for i in add remove fix security skip; do [ -f changelog.d/"$CI_MERGE_REQUEST_IID"."$i" ] retcode=$? if [ $retcode -eq 0 ]; then echo "found $CI_MERGE_REQUEST_IID.$i" - count=$(( count++ )) + count=$(( count + 1 )) else echo "no $CI_MERGE_REQUEST_IID.$i" fi From e444832bb5d0a02ca1a0988c3490d72b99758899 Mon Sep 17 00:00:00 2001 From: Duponin Date: Mon, 10 Apr 2023 20:17:30 +0000 Subject: [PATCH 0142/1008] fix OTP install documentation 'su pleroma' will never work if 'pleroma' user has no password, which is better for security purpose. If admin has no 'sudo' binary, I'm expecting them to be skilled enough to make their way on their own. --- docs/installation/otp_en.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index 13f9636f3..e7dec9039 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -111,13 +111,13 @@ adduser --system --shell /bin/false --home /opt/pleroma pleroma export FLAVOUR="amd64-musl" # Clone the release build into a temporary directory and unpack it -su pleroma -s $SHELL -lc " +sudo -u pleroma -s $SHELL -lc " curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip unzip /tmp/pleroma.zip -d /tmp/ " # Move the release to the home directory and delete temporary files -su pleroma -s $SHELL -lc " +sudo -u pleroma -s $SHELL -lc " mv /tmp/release/* /opt/pleroma rmdir /tmp/release rm /tmp/pleroma.zip @@ -138,25 +138,25 @@ mkdir -p /etc/pleroma chown -R pleroma /etc/pleroma # Run the config generator -su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" +sudo -u pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" # Create the postgres database -su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" +sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" # Create the database schema -su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" +sudo -u pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" # If you have installed RUM indexes uncommend and run -# su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" +# sudo -u pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" # Start the instance to verify that everything is working as expected -su pleroma -s $SHELL -lc "./bin/pleroma daemon" +sudo -u pleroma -s $SHELL -lc "./bin/pleroma daemon" # Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly sleep 20 && curl http://localhost:4000/api/v1/instance # Stop the instance -su pleroma -s $SHELL -lc "./bin/pleroma stop" +sudo -u pleroma -s $SHELL -lc "./bin/pleroma stop" ``` ### Setting up nginx and getting Let's Encrypt SSL certificaties From 7997ba0abed74e45c54d10b634f283d0016a447f Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Apr 2023 11:49:33 -0400 Subject: [PATCH 0143/1008] Build images with kaniko --- .gitlab-ci.yml | 176 +++++++++++++++++++++++++------------------------ 1 file changed, 91 insertions(+), 85 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2e4897aa0..920463fd4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -373,104 +373,110 @@ arm64-musl: before_script: *before-release-musl script: *release -docker: +.kaniko: stage: docker - image: docker:latest + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] cache: {} dependencies: [] - variables: &docker-variables - DOCKER_DRIVER: overlay2 - DOCKER_HOST: unix:///var/run/docker.sock - IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - IMAGE_TAG_SLUG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - IMAGE_TAG_LATEST: $CI_REGISTRY_IMAGE:latest - IMAGE_TAG_LATEST_STABLE: $CI_REGISTRY_IMAGE:latest-stable - DOCKER_BUILDX_URL: https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64 - DOCKER_BUILDX_HASH: 980e6b9655f971991fbbb5fd6cd19f1672386195 - before_script: &before-docker - - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - docker pull $IMAGE_TAG_SLUG || true + before_script: &before-kaniko - export CI_JOB_TIMESTAMP=$(date --utc -Iseconds) - export CI_VCS_REF=$CI_COMMIT_SHORT_SHA - allow_failure: true - script: - - mkdir -p /root/.docker/cli-plugins - - wget "${DOCKER_BUILDX_URL}" -O ~/.docker/cli-plugins/docker-buildx - - echo "${DOCKER_BUILDX_HASH} /root/.docker/cli-plugins/docker-buildx" | sha1sum -c - - chmod +x ~/.docker/cli-plugins/docker-buildx - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - docker buildx create --name mbuilder --driver docker-container --use - - docker buildx inspect --bootstrap - - docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --push --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST . - tags: - - dind + - export IMAGE_TAG=$CI_REGISTRY_IMAGE/$BUILD_ARCH:$CI_COMMIT_SHORT_SHA + - export IMAGE_TAG_SLUG=$CI_REGISTRY_IMAGE/$BUILD_ARCH:$CI_COMMIT_REF_SLUG + - export IMAGE_TAG_LATEST=$CI_REGISTRY_IMAGE/$BUILD_ARCH:latest + - export IMAGE_TAG_LATEST_STABLE=$CI_REGISTRY_IMAGE/$BUILD_ARCH:latest-stable + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + +.kaniko-latest: + extends: .kaniko only: - develop@pleroma/pleroma - -docker-stable: - stage: docker - image: docker:latest - cache: {} - dependencies: [] - variables: *docker-variables - before_script: *before-docker - allow_failure: true script: - - mkdir -p /root/.docker/cli-plugins - - wget "${DOCKER_BUILDX_URL}" -O ~/.docker/cli-plugins/docker-buildx - - echo "${DOCKER_BUILDX_HASH} /root/.docker/cli-plugins/docker-buildx" | sha1sum -c - - chmod +x ~/.docker/cli-plugins/docker-buildx - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - docker buildx create --name mbuilder --driver docker-container --use - - docker buildx inspect --bootstrap - - docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --push --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG -t $IMAGE_TAG_LATEST_STABLE . - tags: - - dind + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST + +.kaniko-stable: + extends: .kaniko only: - stable@pleroma/pleroma + script: + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST_STABLE -docker-release: - stage: docker - image: docker:latest - cache: {} - dependencies: [] - variables: *docker-variables - before_script: *before-docker - allow_failure: true - script: - script: - - mkdir -p /root/.docker/cli-plugins - - wget "${DOCKER_BUILDX_URL}" -O ~/.docker/cli-plugins/docker-buildx - - echo "${DOCKER_BUILDX_HASH} /root/.docker/cli-plugins/docker-buildx" | sha1sum -c - - chmod +x ~/.docker/cli-plugins/docker-buildx - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - docker buildx create --name mbuilder --driver docker-container --use - - docker buildx inspect --bootstrap - - docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --push --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG . - tags: - - dind +.kaniko-release: + extends: .kaniko only: - /^release/.*$/@pleroma/pleroma + script: + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG -docker-adhoc: - stage: docker - image: docker:latest - cache: {} - dependencies: [] - variables: *docker-variables - before_script: *before-docker - allow_failure: true - script: - script: - - mkdir -p /root/.docker/cli-plugins - - wget "${DOCKER_BUILDX_URL}" -O ~/.docker/cli-plugins/docker-buildx - - echo "${DOCKER_BUILDX_HASH} /root/.docker/cli-plugins/docker-buildx" | sha1sum -c - - chmod +x ~/.docker/cli-plugins/docker-buildx - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - - docker buildx create --name mbuilder --driver docker-container --use - - docker buildx inspect --bootstrap - - docker buildx build --platform linux/amd64,linux/arm/v7,linux/arm64/v8 --push --cache-from $IMAGE_TAG_SLUG --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP -t $IMAGE_TAG -t $IMAGE_TAG_SLUG . - tags: - - dind +.kaniko-adhoc: + extends: .kaniko only: - /^build-docker/.*$/@pleroma/pleroma + script: + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG + +.kaniko:linux/amd64: + variables: + BUILD_ARCH: linux/amd64 + tags: + - amd64 + +.kaniko:linux/arm64: + variables: + BUILD_ARCH: linux/arm64 + tags: + - arm + +.kaniko:linux/arm: + variables: + BUILD_ARCH: linux/arm + tags: + - arm32-specified + +kaniko-latest:linux/amd64: + extends: + - .kaniko-latest + - .kaniko:linux/amd64 + +kaniko-latest:linux/arm64: + extends: + - .kaniko-latest + - .kaniko:linux/arm64 + +kaniko-latest:linux/arm: + extends: + - .kaniko-latest + - .kaniko:linux/arm + +kaniko-stable:linux/amd64: + extends: + - .kaniko-stable + - .kaniko:linux/amd64 + +kaniko-stable:linux/arm64: + extends: + - .kaniko-stable + - .kaniko:linux/arm64 + +kaniko-stable:linux/arm: + extends: + - .kaniko-stable + - .kaniko:linux/arm + +kaniko-release:linux/amd64: + extends: + - .kaniko-release + - .kaniko:linux/amd64 + +kaniko-release:linux/arm64: + extends: + - .kaniko-release + - .kaniko:linux/arm64 + +kaniko-release:linux/arm: + extends: + - .kaniko-release + - .kaniko:linux/arm From b37a90caa3ce01930a527e8f9d9b5e877efbb73b Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Apr 2023 12:38:47 -0400 Subject: [PATCH 0144/1008] Combine images of different platforms into one --- .gitlab-ci.yml | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 920463fd4..af882083e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,6 +31,7 @@ stages: - deploy - release - docker + - docker-combine before_script: - echo $MIX_ENV @@ -380,6 +381,14 @@ arm64-musl: entrypoint: [""] cache: {} dependencies: [] + needs: + - spec-build + - unit-testing + - unit-testing-erratic + - unit-testing-rum + - lint + - analysis + - cycles before_script: &before-kaniko - export CI_JOB_TIMESTAMP=$(date --utc -Iseconds) - export CI_VCS_REF=$CI_COMMIT_SHORT_SHA @@ -480,3 +489,65 @@ kaniko-release:linux/arm: extends: - .kaniko-release - .kaniko:linux/arm + +.docker-combine: + stage: docker-combine + image: docker:cli + cache: {} + before_script: + - 'BUILD_ARCHES="linux/amd64 linux/arm64/v8 linux/arm/v7"' + - export IMAGE_TAG=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA + - export IMAGE_TAG_SLUG=$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + - export IMAGE_TAG_LATEST=$CI_REGISTRY_IMAGE:latest + - export IMAGE_TAG_LATEST_STABLE=$CI_REGISTRY_IMAGE:latest-stable + - 'IMAGES=; for arch in $BUILD_ARCHES; do IMAGES="$IMAGES $CI_REGISTRY_IMAGE/$arch:$CI_COMMIT_SHORT_SHA"; done' + - 'IMAGES_SLUG=; for arch in $BUILD_ARCHES; do IMAGES_SLUG="$IMAGES_SLUG $CI_REGISTRY_IMAGE/$arch:$CI_COMMIT_REF_SLUG"; done' + - 'IMAGES_LATEST=; for arch in $BUILD_ARCHES; do IMAGES_LATEST="$IMAGES_LATEST $CI_REGISTRY_IMAGE/$arch:latest"; done' + - 'IMAGES_LATEST_STABLE=; for arch in $BUILD_ARCHES; do IMAGES_LATEST_STABLE="$IMAGES_LATEST_STABLE $CI_REGISTRY_IMAGE/$arch:latest"; done' + - echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY --username $CI_REGISTRY_USER --password-stdin + +docker-combine:latest: + extends: .docker-combine + only: + - develop@pleroma/pleroma + needs: + - 'kaniko-latest:linux/amd64' + - 'kaniko-latest:linux/arm64' + - 'kaniko-latest:linux/arm' + script: + - 'docker manifest create $IMAGE_TAG $IMAGES' + - 'docker manifest push $IMAGE_TAG' + - 'docker manifest create $IMAGE_TAG_SLUG $IMAGES_SLUG' + - 'docker manifest push $IMAGE_TAG_SLUG' + - 'docker manifest create $IMAGE_TAG_LATEST $IMAGES_LATEST' + - 'docker manifest push $IMAGE_TAG_LATEST' + +docker-combine:stable: + extends: .docker-combine + only: + - stable@pleroma/pleroma + needs: + - 'kaniko-stable:linux/amd64' + - 'kaniko-stable:linux/arm64' + - 'kaniko-stable:linux/arm' + script: + - 'docker manifest create $IMAGE_TAG $IMAGES' + - 'docker manifest push $IMAGE_TAG' + - 'docker manifest create $IMAGE_TAG_SLUG $IMAGES_SLUG' + - 'docker manifest push $IMAGE_TAG_SLUG' + - 'docker manifest create $IMAGE_TAG_LATEST_STABLE $IMAGES_LATEST_STABLE' + - 'docker manifest push $IMAGE_TAG_LATEST_STABLE' + +docker-combine:release: + extends: .docker-combine + only: + - /^release/.*$/@pleroma/pleroma + needs: + - 'kaniko-release:linux/amd64' + - 'kaniko-release:linux/arm64' + - 'kaniko-release:linux/arm' + script: + - 'docker manifest create $IMAGE_TAG $IMAGES' + - 'docker manifest push $IMAGE_TAG' + - 'docker manifest create $IMAGE_TAG_SLUG $IMAGES_SLUG' + - 'docker manifest push $IMAGE_TAG_SLUG' From 23bca0c4b3b19a6f82158bb35743cf035f497ef4 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Apr 2023 12:40:26 -0400 Subject: [PATCH 0145/1008] Skip changelog entry --- changelog.d/3870.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/3870.skip diff --git a/changelog.d/3870.skip b/changelog.d/3870.skip new file mode 100644 index 000000000..e69de29bb From 7944271c1bdf4a4240b682249c068deb1b2acab0 Mon Sep 17 00:00:00 2001 From: Duponin Date: Sat, 15 Apr 2023 19:03:51 +0000 Subject: [PATCH 0146/1008] Unify install guides using sudo to use `sudo -Hu` --- docs/installation/migrating_from_source_otp_en.md | 8 ++++---- docs/installation/otp_en.md | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/installation/migrating_from_source_otp_en.md b/docs/installation/migrating_from_source_otp_en.md index d303a6daf..64d335331 100644 --- a/docs/installation/migrating_from_source_otp_en.md +++ b/docs/installation/migrating_from_source_otp_en.md @@ -85,26 +85,26 @@ export FLAVOUR="amd64-musl" # Clone the release build into a temporary directory and unpack it # Replace `stable` with `unstable` if you want to run the unstable branch -su pleroma -s $SHELL -lc " +sudo -Hu pleroma " curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip unzip /tmp/pleroma.zip -d /tmp/ " # Move the release to the home directory and delete temporary files -su pleroma -s $SHELL -lc " +sudo -Hu pleroma " mv /tmp/release/* ~pleroma/ rmdir /tmp/release rm /tmp/pleroma.zip " # Start the instance to verify that everything is working as expected -su pleroma -s $SHELL -lc "./bin/pleroma daemon" +sudo -Hu pleroma "./bin/pleroma daemon" # Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly sleep 20 && curl http://localhost:4000/api/v1/instance # Stop the instance -su pleroma -s $SHELL -lc "./bin/pleroma stop" +sudo -Hu pleroma "./bin/pleroma stop" ``` ## Setting up a system service diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index e7dec9039..12078cb20 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -111,13 +111,13 @@ adduser --system --shell /bin/false --home /opt/pleroma pleroma export FLAVOUR="amd64-musl" # Clone the release build into a temporary directory and unpack it -sudo -u pleroma -s $SHELL -lc " +sudo -Hu pleroma " curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip unzip /tmp/pleroma.zip -d /tmp/ " # Move the release to the home directory and delete temporary files -sudo -u pleroma -s $SHELL -lc " +sudo -Hu pleroma " mv /tmp/release/* /opt/pleroma rmdir /tmp/release rm /tmp/pleroma.zip @@ -138,25 +138,25 @@ mkdir -p /etc/pleroma chown -R pleroma /etc/pleroma # Run the config generator -sudo -u pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" +sudo -Hu pleroma "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" # Create the postgres database sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" # Create the database schema -sudo -u pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate" +sudo -Hu pleroma "./bin/pleroma_ctl migrate" # If you have installed RUM indexes uncommend and run -# sudo -u pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" +# sudo -Hu pleroma "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" # Start the instance to verify that everything is working as expected -sudo -u pleroma -s $SHELL -lc "./bin/pleroma daemon" +sudo -Hu pleroma "./bin/pleroma daemon" # Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly sleep 20 && curl http://localhost:4000/api/v1/instance # Stop the instance -sudo -u pleroma -s $SHELL -lc "./bin/pleroma stop" +sudo -Hu pleroma "./bin/pleroma stop" ``` ### Setting up nginx and getting Let's Encrypt SSL certificaties From 8f0f58e28b58cd9a4a05f1b927aa24fd63f9a3bd Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 17 Apr 2023 21:07:08 +0200 Subject: [PATCH 0147/1008] UploadedMedia: Add missing disposition_type to Content-Disposition Set it to `inline` because the vast majority of what's sent is multimedia content while `attachment` would have the side-effect of triggering a download dialog. Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3114 --- changelog.d/3873.fix | 1 + lib/pleroma/web/plugs/uploaded_media.ex | 2 +- test/pleroma/web/plugs/uploaded_media_plug_test.exs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog.d/3873.fix diff --git a/changelog.d/3873.fix b/changelog.d/3873.fix new file mode 100644 index 000000000..4699f7b58 --- /dev/null +++ b/changelog.d/3873.fix @@ -0,0 +1 @@ +UploadedMedia: Add missing disposition_type to Content-Disposition \ No newline at end of file diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index ad8143234..71d8f2f74 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -37,7 +37,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do %{query_params: %{"name" => name}} = conn -> name = String.replace(name, "\"", "\\\"") - put_resp_header(conn, "content-disposition", "filename=\"#{name}\"") + put_resp_header(conn, "content-disposition", "inline; filename=\"#{name}\"") conn -> conn diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs index ec46b0537..99ba49d37 100644 --- a/test/pleroma/web/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -37,7 +37,7 @@ defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do assert Enum.any?( conn.resp_headers, - &(&1 == {"content-disposition", "filename=\"\\\"cofe\\\".gif\""}) + &(&1 == {"content-disposition", "inline; filename=\"\\\"cofe\\\".gif\""}) ) end end From 2148ef5e2fcced6a52f0cfda38abcf697698dd1a Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 18 Apr 2023 00:07:39 +0200 Subject: [PATCH 0148/1008] UploadedMedia: Increase readability via ~s sigil --- lib/pleroma/web/plugs/uploaded_media.ex | 4 ++-- test/pleroma/web/plugs/uploaded_media_plug_test.exs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 71d8f2f74..8b3bc9acb 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -35,9 +35,9 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do conn = case fetch_query_params(conn) do %{query_params: %{"name" => name}} = conn -> - name = String.replace(name, "\"", "\\\"") + name = String.replace(name, ~s["], ~s[\\"]) - put_resp_header(conn, "content-disposition", "inline; filename=\"#{name}\"") + put_resp_header(conn, "content-disposition", ~s[inline; filename="#{name}"]) conn -> conn diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs index 99ba49d37..8323ff6ab 100644 --- a/test/pleroma/web/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -33,11 +33,11 @@ defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do test "sends Content-Disposition header when name param is set", %{ attachment_url: attachment_url } do - conn = get(build_conn(), attachment_url <> "?name=\"cofe\".gif") + conn = get(build_conn(), attachment_url <> ~s[?name="cofe".gif]) assert Enum.any?( conn.resp_headers, - &(&1 == {"content-disposition", "inline; filename=\"\\\"cofe\\\".gif\""}) + &(&1 == {"content-disposition", ~s[inline; filename="\\"cofe\\".gif"]}) ) end end From d91a823836f416dc0bdf0fe8527695d372dcb347 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 22 Apr 2023 16:14:56 +0000 Subject: [PATCH 0149/1008] Remove unused indexes These indexes were always listed as unused and several grow quite large. The most significant impact is the activities_visibility_index which takes many hours to rebuild when restoring the server from backup even on fast hardware. --- .../20230422154018_drop_unused_indexes.exs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 priv/repo/migrations/20230422154018_drop_unused_indexes.exs diff --git a/priv/repo/migrations/20230422154018_drop_unused_indexes.exs b/priv/repo/migrations/20230422154018_drop_unused_indexes.exs new file mode 100644 index 000000000..f2ba6b8df --- /dev/null +++ b/priv/repo/migrations/20230422154018_drop_unused_indexes.exs @@ -0,0 +1,88 @@ +defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do + use Ecto.Migration + + @disable_ddl_transaction true + @disable_migration_lock true + + def up do + drop_if_exists( + index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index) + ) + + drop_if_exists(index(:activities, ["(data->'to')"], name: :activities_to_index)) + + drop_if_exists(index(:activities, ["(data->'cc')"], name: :activities_cc_index)) + + drop_if_exists(index(:activities, ["(split_part(actor, '/', 3))"], name: :activities_hosts)) + + drop_if_exists( + index(:activities, ["(data->'object'->>'inReplyTo')"], name: :activities_in_reply_to) + ) + + drop_if_exists( + index(:activities, ["((data #> '{\"object\",\"likes\"}'))"], name: :activities_likes) + ) + + drop_if_exists( + index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC NULLS LAST"], + name: :activities_visibility_index, + where: "data->>'type' = 'Create'" + ) + ) + end + + def down do + create_if_not_exists( + index(:activities, ["(data->>'actor')", "inserted_at desc"], + name: :activities_actor_index, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(data->'to')"], + name: :activities_to_index, + using: :gin, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(data->'cc')"], + name: :activities_cc_index, + using: :gin, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(split_part(actor, '/', 3))"], + name: :activities_hosts, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(data->'object'->>'inReplyTo')"], + name: :activities_in_reply_to, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["((data #> '{\"object\",\"likes\"}'))"], + name: :activities_likes, + using: :gin, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC NULLS LAST"], + name: :activities_visibility_index, + where: "data->>'type' = 'Create'", + concurrently: true + ) + ) + end +end From 50e237759a0aa378b32393ba57d50eaaff5029e0 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 22 Apr 2023 20:30:17 -0400 Subject: [PATCH 0150/1008] Use git diff to search for changelog entry --- .gitlab-ci.yml | 1 + tools/check-changelog | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2e4897aa0..6e498bf89 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -49,6 +49,7 @@ check-changelog: after_script: '' cache: {} script: + - apk add git - sh ./tools/check-changelog build: diff --git a/tools/check-changelog b/tools/check-changelog index b94b52755..0b57c4d1e 100644 --- a/tools/check-changelog +++ b/tools/check-changelog @@ -1,22 +1,15 @@ #!/bin/sh -echo "looking for change log of $CI_MERGE_REQUEST_IID" +echo "looking for change log" -count=0 -for i in add remove fix security skip; do - [ -f changelog.d/"$CI_MERGE_REQUEST_IID"."$i" ] - retcode=$? - if [ $retcode -eq 0 ]; then - echo "found $CI_MERGE_REQUEST_IID.$i" - count=$(( count + 1 )) - else - echo "no $CI_MERGE_REQUEST_IID.$i" - fi -done -if [ $count -gt 0 ]; then - echo "ok" +git diff --raw $CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \ + grep ' A\t' | grep '\.\(skip\|add\|remove\|fix\|security\)$' +ret=$? + +if [ $ret -eq 0 ]; then + echo "found a changelog entry" exit 0 else - echo "must have a changelog entry or explicitly skip it" + echo "changelog entry not found" exit 1 fi From e13b331762881933d66597b3145b4d20d6e51912 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 22 Apr 2023 20:40:39 -0400 Subject: [PATCH 0151/1008] Fetch upstream in the repo --- tools/check-changelog | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/check-changelog b/tools/check-changelog index 0b57c4d1e..1409be9f0 100644 --- a/tools/check-changelog +++ b/tools/check-changelog @@ -2,7 +2,10 @@ echo "looking for change log" -git diff --raw $CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \ +git remote add upstream https://git.pleroma.social/pleroma/pleroma.git +git fetch upstream ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}:refs/remotes/upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME + +git diff --raw upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \ grep ' A\t' | grep '\.\(skip\|add\|remove\|fix\|security\)$' ret=$? From c1aa83069daaa7df906f63b3417e14b7edbfae94 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 22 Apr 2023 20:45:27 -0400 Subject: [PATCH 0152/1008] Skip changelog --- changelog.d/changelog-improve.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/changelog-improve.skip diff --git a/changelog.d/changelog-improve.skip b/changelog.d/changelog-improve.skip new file mode 100644 index 000000000..e69de29bb From 30bc37c3cab8ebf7be667fb291f0d99c17d808ef Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 22 Apr 2023 21:02:13 -0400 Subject: [PATCH 0153/1008] Explain changelog.d in merge request templates --- .gitlab/merge_request_templates/Default.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .gitlab/merge_request_templates/Default.md diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md new file mode 100644 index 000000000..fdf219f99 --- /dev/null +++ b/.gitlab/merge_request_templates/Default.md @@ -0,0 +1,10 @@ +### Checklist +- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `.`. + + `` can be anything, but we recommend using a more or less unique identifier to avoid collisions, such as the branch name. + + `` can be `add`, `remove`, `fix`, `security` or `skip`. `skip` is only used if there is no user-visible change in the MR (for example, only editing comments in the code). Otherwise, choose a type that corresponds to your change. + + In the file, write the changelog entry. For example, if an MR adds group functionality, we can create a file named `group.add` and write `Add group functionality` in it. + + If one changelog entry is not enough, you may add more. But that might mean you can split it into two MRs. Only use more than one changelog entry if you really need to (for example, when one change in the code fix two different bugs, or when refactoring). From ae8f359f22d8d02c2937df0a81052fc3504d4248 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 22 Apr 2023 21:07:18 -0400 Subject: [PATCH 0154/1008] Skip changelog check for automated MRs --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6e498bf89..2eee3bc68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -44,6 +44,10 @@ check-changelog: stage: check-changelog image: alpine rules: + - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate-extract' + when: never + - if: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH == 'pleroma/pleroma' && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == 'weblate' + when: never - if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop" before_script: '' after_script: '' From 0231a09310895bd2af24eff221de97eb8c92307e Mon Sep 17 00:00:00 2001 From: duponin Date: Sun, 16 Apr 2023 15:50:32 +0200 Subject: [PATCH 0155/1008] Remove SSH/BBS feature from core And link to sshocial, the replacement client for this removed feature --- config/config.exs | 3 - config/description.exs | 39 --- docs/configuration/cheatsheet.md | 17 +- lib/pleroma/bbs/authenticator.ex | 20 -- lib/pleroma/bbs/handler.ex | 246 ------------------ mix.exs | 4 +- mix.lock | 1 - test/pleroma/bbs/handler_test.exs | 89 ------- .../controllers/config_controller_test.exs | 6 +- 9 files changed, 4 insertions(+), 421 deletions(-) delete mode 100644 lib/pleroma/bbs/authenticator.ex delete mode 100644 lib/pleroma/bbs/handler.ex delete mode 100644 test/pleroma/bbs/handler_test.exs diff --git a/config/config.exs b/config/config.exs index e41ec2f91..178b6be99 100644 --- a/config/config.exs +++ b/config/config.exs @@ -617,9 +617,6 @@ config :pleroma, :ldap, base: System.get_env("LDAP_BASE") || "dc=example,dc=com", uid: System.get_env("LDAP_UID") || "cn" -config :esshd, - enabled: false - oauth_consumer_strategies = System.get_env("OAUTH_CONSUMER_STRATEGIES") |> to_string() diff --git a/config/description.exs b/config/description.exs index 78dc8770d..5f8add8a4 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2628,45 +2628,6 @@ config :pleroma, :config_description, [ } ] }, - %{ - group: :esshd, - label: "ESSHD", - type: :group, - description: - "Before enabling this you must add :esshd to mix.exs as one of the extra_applications " <> - "and generate host keys in your priv dir with ssh-keygen -m PEM -N \"\" -b 2048 -t rsa -f ssh_host_rsa_key", - children: [ - %{ - key: :enabled, - type: :boolean, - description: "Enables SSH" - }, - %{ - key: :priv_dir, - type: :string, - description: "Dir with SSH keys", - suggestions: ["/some/path/ssh_keys"] - }, - %{ - key: :handler, - type: :string, - description: "Handler module", - suggestions: ["Pleroma.BBS.Handler"] - }, - %{ - key: :port, - type: :integer, - description: "Port to connect", - suggestions: [10_022] - }, - %{ - key: :password_authenticator, - type: :string, - description: "Authenticator module", - suggestions: ["Pleroma.BBS.Authenticator"] - } - ] - }, %{ group: :mime, label: "Mime Types", diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index bbdf30a0f..70abe3e0c 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -873,21 +873,8 @@ This will probably take a long time. ### BBS / SSH access -To enable simple command line interface accessible over ssh, add a setting like this to your configuration file: - -```exs -app_dir = File.cwd! -priv_dir = Path.join([app_dir, "priv/ssh_keys"]) - -config :esshd, - enabled: true, - priv_dir: priv_dir, - handler: "Pleroma.BBS.Handler", - port: 10_022, - password_authenticator: "Pleroma.BBS.Authenticator" -``` - -Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT` +This feature has been removed from Pleroma core. +However, a client has been made and is available at https://git.pleroma.social/Duponin/sshocial. ### :gopher * `enabled`: Enables the gopher interface diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex deleted file mode 100644 index 0f7543ff5..000000000 --- a/lib/pleroma/bbs/authenticator.ex +++ /dev/null @@ -1,20 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.BBS.Authenticator do - use Sshd.PasswordAuthenticator - alias Pleroma.User - alias Pleroma.Web.Plugs.AuthenticationPlug - - def authenticate(username, password) do - username = to_string(username) - password = to_string(password) - - with %User{} = user <- User.get_by_nickname(username) do - AuthenticationPlug.checkpw(password, user.password_hash) - else - _e -> false - end - end -end diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex deleted file mode 100644 index 27799338f..000000000 --- a/lib/pleroma/bbs/handler.ex +++ /dev/null @@ -1,246 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.BBS.Handler do - use Sshd.ShellHandler - alias Pleroma.Activity - alias Pleroma.HTML - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.CommonAPI - - def on_shell(username, _pubkey, _ip, _port) do - :ok = IO.puts("Welcome to #{Pleroma.Config.get([:instance, :name])}!") - user = Pleroma.User.get_cached_by_nickname(to_string(username)) - Logger.debug("#{inspect(user)}") - loop(run_state(user: user)) - end - - def on_connect(username, ip, port, method) do - Logger.debug(fn -> - """ - Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)} - """ - end) - end - - def on_disconnect(username, ip, port) do - Logger.debug(fn -> - "Disconnecting SSH shell for #{username} from #{inspect(ip)}:#{inspect(port)}" - end) - end - - defp loop(state) do - self_pid = self() - counter = state.counter - prefix = state.prefix - user = state.user - - input = spawn(fn -> io_get(self_pid, prefix, counter, user.nickname) end) - wait_input(state, input) - end - - def puts_activity(activity) do - status = Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{activity: activity}) - - IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})") - - status.content - |> String.split("
") - |> Enum.map(&HTML.strip_tags/1) - |> Enum.map(&HtmlEntities.decode/1) - |> Enum.map(&IO.puts/1) - end - - def puts_notification(activity, user) do - notification = - Pleroma.Web.MastodonAPI.NotificationView.render("show.json", %{ - notification: activity, - for: user - }) - - IO.puts( - "== (#{notification.type}) #{notification.status.id} by #{notification.account.display_name} (#{notification.account.acct})" - ) - - notification.status.content - |> String.split("
") - |> Enum.map(&HTML.strip_tags/1) - |> Enum.map(&HtmlEntities.decode/1) - |> (fn x -> - case x do - [content] -> - "> " <> content - - [head | _tail] -> - # "> " <> hd <> "..." - head - |> String.slice(1, 80) - |> (fn x -> "> " <> x <> "..." end).() - end - end).() - |> IO.puts() - - IO.puts("") - end - - def handle_command(state, "help") do - IO.puts("Available commands:") - IO.puts("help - This help") - IO.puts("home - Show the home timeline") - IO.puts("p - Post the given text") - IO.puts("r - Reply to the post with the given id") - IO.puts("t - Show a thread from the given id") - IO.puts("n - Show notifications") - IO.puts("n read - Mark all notifactions as read") - IO.puts("f - Favourites the post with the given id") - IO.puts("R - Repeat the post with the given id") - IO.puts("quit - Quit") - - state - end - - def handle_command(%{user: user} = state, "r " <> text) do - text = String.trim(text) - [activity_id, rest] = String.split(text, " ", parts: 2) - - with %Activity{} <- Activity.get_by_id(activity_id), - {:ok, _activity} <- - CommonAPI.post(user, %{status: rest, in_reply_to_status_id: activity_id}) do - IO.puts("Replied!") - else - _e -> IO.puts("Could not reply...") - end - - state - end - - def handle_command(%{user: user} = state, "t " <> activity_id) do - with %Activity{} = activity <- Activity.get_by_id(activity_id) do - activities = - ActivityPub.fetch_activities_for_context(activity.data["context"], %{ - blocking_user: user, - user: user, - exclude_id: activity.id - }) - - case activities do - [] -> - activity_id - |> Activity.get_by_id() - |> puts_activity() - - _ -> - activities - |> Enum.reverse() - |> Enum.each(&puts_activity/1) - end - else - _e -> IO.puts("Could not show this thread...") - end - - state - end - - def handle_command(%{user: user} = state, "n read") do - Pleroma.Notification.clear(user) - IO.puts("All notifications were marked as read") - - state - end - - def handle_command(%{user: user} = state, "n") do - user - |> Pleroma.Web.MastodonAPI.MastodonAPI.get_notifications(%{}) - |> Enum.each(&puts_notification(&1, user)) - - state - end - - def handle_command(%{user: user} = state, "p " <> text) do - text = String.trim(text) - - with {:ok, activity} <- CommonAPI.post(user, %{status: text}) do - IO.puts("Posted! ID: #{activity.id}") - else - _e -> IO.puts("Could not post...") - end - - state - end - - def handle_command(%{user: user} = state, "f " <> id) do - id = String.trim(id) - - with %Activity{} = activity <- Activity.get_by_id(id), - {:ok, _activity} <- CommonAPI.favorite(user, activity) do - IO.puts("Favourited!") - else - _e -> IO.puts("Could not Favourite...") - end - - state - end - - def handle_command(state, "home") do - user = state.user - - params = - %{} - |> Map.put(:type, ["Create"]) - |> Map.put(:blocking_user, user) - |> Map.put(:muting_user, user) - |> Map.put(:user, user) - - activities = - [user.ap_id | Pleroma.User.following(user)] - |> ActivityPub.fetch_activities(params) - - Enum.each(activities, fn activity -> - puts_activity(activity) - end) - - state - end - - def handle_command(state, command) do - IO.puts("Unknown command '#{command}'") - state - end - - defp wait_input(state, input) do - receive do - {:input, ^input, "quit\n"} -> - IO.puts("Exiting...") - - {:input, ^input, code} when is_binary(code) -> - code = String.trim(code) - - state = handle_command(state, code) - - loop(%{state | counter: state.counter + 1}) - - {:input, ^input, {:error, :interrupted}} -> - IO.puts("Caught Ctrl+C...") - loop(%{state | counter: state.counter + 1}) - - {:input, ^input, msg} -> - :ok = Logger.warn("received unknown message: #{inspect(msg)}") - loop(%{state | counter: state.counter + 1}) - end - end - - defp run_state(opts) do - %{prefix: "pleroma", counter: 1, user: opts[:user]} - end - - defp io_get(pid, prefix, counter, username) do - prompt = prompt(prefix, counter, username) - send(pid, {:input, self(), IO.gets(:stdio, prompt)}) - end - - defp prompt(prefix, counter, username) do - prompt = "#{username}@#{prefix}:#{counter}>" - prompt <> " " - end -end diff --git a/mix.exs b/mix.exs index 027442e33..afce46d22 100644 --- a/mix.exs +++ b/mix.exs @@ -78,8 +78,7 @@ defmodule Pleroma.Mixfile do :comeonin, :fast_sanitize, :os_mon, - :ssl, - :esshd + :ssl ], included_applications: [:ex_syslogger] ] @@ -181,7 +180,6 @@ defmodule Pleroma.Mixfile do {:joken, "~> 2.0"}, {:benchee, "~> 1.0"}, {:pot, "~> 1.0"}, - {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)}, {:ex_const, "~> 0.2"}, {:plug_static_index_html, "~> 1.0.0"}, {:flake_id, "~> 0.1.0"}, diff --git a/mix.lock b/mix.lock index 0af2c4292..43012a58e 100644 --- a/mix.lock +++ b/mix.lock @@ -35,7 +35,6 @@ "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, - "esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, diff --git a/test/pleroma/bbs/handler_test.exs b/test/pleroma/bbs/handler_test.exs deleted file mode 100644 index aea3b6ead..000000000 --- a/test/pleroma/bbs/handler_test.exs +++ /dev/null @@ -1,89 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.BBS.HandlerTest do - use Pleroma.DataCase, async: true - alias Pleroma.Activity - alias Pleroma.BBS.Handler - alias Pleroma.Object - alias Pleroma.Repo - alias Pleroma.User - alias Pleroma.Web.CommonAPI - - import ExUnit.CaptureIO - import Pleroma.Factory - import Ecto.Query - - test "getting the home timeline" do - user = insert(:user) - followed = insert(:user) - - {:ok, user, followed} = User.follow(user, followed) - - {:ok, _first} = CommonAPI.post(user, %{status: "hey"}) - {:ok, _second} = CommonAPI.post(followed, %{status: "hello"}) - - output = - capture_io(fn -> - Handler.handle_command(%{user: user}, "home") - end) - - assert output =~ user.nickname - assert output =~ followed.nickname - - assert output =~ "hey" - assert output =~ "hello" - end - - test "posting" do - user = insert(:user) - - output = - capture_io(fn -> - Handler.handle_command(%{user: user}, "p this is a test post") - end) - - assert output =~ "Posted" - - activity = - Repo.one( - from(a in Activity, - where: fragment("?->>'type' = ?", a.data, "Create") - ) - ) - - assert activity.actor == user.ap_id - object = Object.normalize(activity, fetch: false) - assert object.data["content"] == "this is a test post" - end - - test "replying" do - user = insert(:user) - another_user = insert(:user) - - {:ok, activity} = CommonAPI.post(another_user, %{status: "this is a test post"}) - activity_object = Object.normalize(activity, fetch: false) - - output = - capture_io(fn -> - Handler.handle_command(%{user: user}, "r #{activity.id} this is a reply") - end) - - assert output =~ "Replied" - - reply = - Repo.one( - from(a in Activity, - where: fragment("?->>'type' = ?", a.data, "Create"), - where: a.actor == ^user.ap_id - ) - ) - - assert reply.actor == user.ap_id - - reply_object_data = Object.normalize(reply, fetch: false).data - assert reply_object_data["content"] == "this is a reply" - assert reply_object_data["inReplyTo"] == activity_object.data["id"] - end -end diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs index 9fb5fb520..f13a9bd42 100644 --- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs @@ -1502,8 +1502,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do clear_config(:database_config_whitelist, [ {:pleroma, :instance}, {:pleroma, :activitypub}, - {:pleroma, Pleroma.Upload}, - {:esshd} + {:pleroma, Pleroma.Upload} ]) conn = get(conn, "/api/pleroma/admin/config/descriptions") @@ -1522,9 +1521,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end) assert web_endpoint["children"] - - esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end) - assert esshd["children"] end end end From b2dc9ad9d8bba1ddc213bc98b68b2d64b172733d Mon Sep 17 00:00:00 2001 From: duponin Date: Sun, 16 Apr 2023 16:03:49 +0200 Subject: [PATCH 0156/1008] fix test after removing esshd/SSH feature --- .../web/admin_api/controllers/config_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs index f13a9bd42..19ce3681c 100644 --- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs @@ -1509,7 +1509,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do children = json_response_and_validate_schema(conn, 200) - assert length(children) == 4 + assert length(children) == 3 assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3 From af38c6104ea0a5724d169dbe63efca7e158dbeea Mon Sep 17 00:00:00 2001 From: duponin Date: Sun, 23 Apr 2023 10:58:50 +0200 Subject: [PATCH 0157/1008] add changelog entry for BBS/SSH feature remove --- changelog.d/3872.remove | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3872.remove diff --git a/changelog.d/3872.remove b/changelog.d/3872.remove new file mode 100644 index 000000000..54cbb660e --- /dev/null +++ b/changelog.d/3872.remove @@ -0,0 +1 @@ +remove BBS/SSH feature, replaced by an external bridge. \ No newline at end of file From 66d23713e9fa16fc48931bb4efccd59d631e2610 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 24 Apr 2023 19:56:54 -0400 Subject: [PATCH 0158/1008] Do not use nested levels for arch --- .gitlab-ci.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index af882083e..8e971ec89 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -392,10 +392,10 @@ arm64-musl: before_script: &before-kaniko - export CI_JOB_TIMESTAMP=$(date --utc -Iseconds) - export CI_VCS_REF=$CI_COMMIT_SHORT_SHA - - export IMAGE_TAG=$CI_REGISTRY_IMAGE/$BUILD_ARCH:$CI_COMMIT_SHORT_SHA - - export IMAGE_TAG_SLUG=$CI_REGISTRY_IMAGE/$BUILD_ARCH:$CI_COMMIT_REF_SLUG - - export IMAGE_TAG_LATEST=$CI_REGISTRY_IMAGE/$BUILD_ARCH:latest - - export IMAGE_TAG_LATEST_STABLE=$CI_REGISTRY_IMAGE/$BUILD_ARCH:latest-stable + - export IMAGE_TAG=$CI_REGISTRY_IMAGE/$BUILD_ARCH_IMG_SUFFIX:$CI_COMMIT_SHORT_SHA + - export IMAGE_TAG_SLUG=$CI_REGISTRY_IMAGE/$BUILD_ARCH_IMG_SUFFIX:$CI_COMMIT_REF_SLUG + - export IMAGE_TAG_LATEST=$CI_REGISTRY_IMAGE/$BUILD_ARCH_IMG_SUFFIX:latest + - export IMAGE_TAG_LATEST_STABLE=$CI_REGISTRY_IMAGE/$BUILD_ARCH_IMG_SUFFIX:latest-stable - mkdir -p /kaniko/.docker - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json @@ -430,18 +430,21 @@ arm64-musl: .kaniko:linux/amd64: variables: BUILD_ARCH: linux/amd64 + BUILD_ARCH_IMG_SUFFIX: linux-amd64 tags: - amd64 .kaniko:linux/arm64: variables: - BUILD_ARCH: linux/arm64 + BUILD_ARCH: linux/arm64/v8 + BUILD_ARCH_IMG_SUFFIX: linux-arm64-v8 tags: - arm .kaniko:linux/arm: variables: - BUILD_ARCH: linux/arm + BUILD_ARCH: linux/arm/v7 + BUILD_ARCH_IMG_SUFFIX: linux-arm-v7 tags: - arm32-specified @@ -495,7 +498,7 @@ kaniko-release:linux/arm: image: docker:cli cache: {} before_script: - - 'BUILD_ARCHES="linux/amd64 linux/arm64/v8 linux/arm/v7"' + - 'BUILD_ARCHES="linux-amd64 linux-arm64-v8 linux-arm-v7"' - export IMAGE_TAG=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - export IMAGE_TAG_SLUG=$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - export IMAGE_TAG_LATEST=$CI_REGISTRY_IMAGE:latest From 2736c3f29406d11ed244c2e165f746f32f8b7273 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 24 Apr 2023 19:57:32 -0400 Subject: [PATCH 0159/1008] Use --custom-platform to replace the deprecated one --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8e971ec89..a98e3ee6b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -404,28 +404,28 @@ arm64-musl: only: - develop@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST .kaniko-stable: extends: .kaniko only: - stable@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST_STABLE + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST_STABLE .kaniko-release: extends: .kaniko only: - /^release/.*$/@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG .kaniko-adhoc: extends: .kaniko only: - /^build-docker/.*$/@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --customPlatform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG .kaniko:linux/amd64: variables: From c5ffdd060955bef1b86506c22dfd57eba6c64e41 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 24 Apr 2023 20:03:59 -0400 Subject: [PATCH 0160/1008] Use self-built elixir image for arm --- .gitlab-ci.yml | 9 ++++++--- Dockerfile | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a98e3ee6b..d0b2bd998 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -411,26 +411,27 @@ arm64-musl: only: - stable@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST_STABLE + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --build-arg ELIXIR_IMG=$ELIXIR_IMG --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST_STABLE .kaniko-release: extends: .kaniko only: - /^release/.*$/@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --build-arg ELIXIR_IMG=$ELIXIR_IMG --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG .kaniko-adhoc: extends: .kaniko only: - /^build-docker/.*$/@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --build-arg ELIXIR_IMG=$ELIXIR_IMG --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG .kaniko:linux/amd64: variables: BUILD_ARCH: linux/amd64 BUILD_ARCH_IMG_SUFFIX: linux-amd64 + ELIXIR_IMG: hexpm/elixir tags: - amd64 @@ -438,6 +439,7 @@ arm64-musl: variables: BUILD_ARCH: linux/arm64/v8 BUILD_ARCH_IMG_SUFFIX: linux-arm64-v8 + ELIXIR_IMG: hexpm/elixir tags: - arm @@ -445,6 +447,7 @@ arm64-musl: variables: BUILD_ARCH: linux/arm/v7 BUILD_ARCH_IMG_SUFFIX: linux-arm-v7 + ELIXIR_IMG: git.pleroma.social:5050/pleroma/ci-image/elixir-linux-arm-v7 tags: - arm32-specified diff --git a/Dockerfile b/Dockerfile index 8c3ff3ac5..310d18104 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ +ARG ELIXIR_IMG=hexpm/elixir ARG ELIXIR_VER=1.11.4 ARG ERLANG_VER=24.2.1 ARG ALPINE_VER=3.17.0 -FROM hexpm/elixir:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build +FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build COPY . . From 9c7b036401457ed801d03dae52bc0854bfa62715 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 25 Apr 2023 21:38:54 -0400 Subject: [PATCH 0161/1008] Add ELIXIR_IMG arg to latest --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d0b2bd998..154083c68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -404,7 +404,7 @@ arm64-musl: only: - develop@pleroma/pleroma script: - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --custom-platform=$BUILD_ARCH --build-arg VCS_REF=$CI_VCS_REF --build-arg BUILD_DATE=$CI_JOB_TIMESTAMP --build-arg ELIXIR_IMG=$ELIXIR_IMG --destination $IMAGE_TAG --destination $IMAGE_TAG_SLUG --destination $IMAGE_TAG_LATEST .kaniko-stable: extends: .kaniko From 47e95fe9f5732b26530045a4cfe81538249c64d3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 25 Apr 2023 21:40:28 -0400 Subject: [PATCH 0162/1008] Add changelog for 3876 --- changelog.d/3876.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/3876.skip diff --git a/changelog.d/3876.skip b/changelog.d/3876.skip new file mode 100644 index 000000000..e69de29bb From a94691720641c47fef9efdaf619abc6f82cdbf29 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 26 Apr 2023 07:18:39 -0400 Subject: [PATCH 0163/1008] Work around docker login needing daemon --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 154083c68..72ac4981d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -510,7 +510,8 @@ kaniko-release:linux/arm: - 'IMAGES_SLUG=; for arch in $BUILD_ARCHES; do IMAGES_SLUG="$IMAGES_SLUG $CI_REGISTRY_IMAGE/$arch:$CI_COMMIT_REF_SLUG"; done' - 'IMAGES_LATEST=; for arch in $BUILD_ARCHES; do IMAGES_LATEST="$IMAGES_LATEST $CI_REGISTRY_IMAGE/$arch:latest"; done' - 'IMAGES_LATEST_STABLE=; for arch in $BUILD_ARCHES; do IMAGES_LATEST_STABLE="$IMAGES_LATEST_STABLE $CI_REGISTRY_IMAGE/$arch:latest"; done' - - echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY --username $CI_REGISTRY_USER --password-stdin + - mkdir -p ~/.docker + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json docker-combine:latest: extends: .docker-combine From 1a50db36d3f40d25525128a45adc23038525d882 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 26 Apr 2023 07:20:35 -0400 Subject: [PATCH 0164/1008] Skip changelog entry for 3877 --- changelog.d/3877.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/3877.skip diff --git a/changelog.d/3877.skip b/changelog.d/3877.skip new file mode 100644 index 000000000..e69de29bb From d5e83459460c4bb5202db8207d590f631285bdee Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 26 Apr 2023 09:14:49 -0400 Subject: [PATCH 0165/1008] Do not use needs: in pipeline yaml --- .gitlab-ci.yml | 20 -------------------- changelog.d/3878.skip | 0 2 files changed, 20 deletions(-) create mode 100644 changelog.d/3878.skip diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 72ac4981d..7e2d487de 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -381,14 +381,6 @@ arm64-musl: entrypoint: [""] cache: {} dependencies: [] - needs: - - spec-build - - unit-testing - - unit-testing-erratic - - unit-testing-rum - - lint - - analysis - - cycles before_script: &before-kaniko - export CI_JOB_TIMESTAMP=$(date --utc -Iseconds) - export CI_VCS_REF=$CI_COMMIT_SHORT_SHA @@ -517,10 +509,6 @@ docker-combine:latest: extends: .docker-combine only: - develop@pleroma/pleroma - needs: - - 'kaniko-latest:linux/amd64' - - 'kaniko-latest:linux/arm64' - - 'kaniko-latest:linux/arm' script: - 'docker manifest create $IMAGE_TAG $IMAGES' - 'docker manifest push $IMAGE_TAG' @@ -533,10 +521,6 @@ docker-combine:stable: extends: .docker-combine only: - stable@pleroma/pleroma - needs: - - 'kaniko-stable:linux/amd64' - - 'kaniko-stable:linux/arm64' - - 'kaniko-stable:linux/arm' script: - 'docker manifest create $IMAGE_TAG $IMAGES' - 'docker manifest push $IMAGE_TAG' @@ -549,10 +533,6 @@ docker-combine:release: extends: .docker-combine only: - /^release/.*$/@pleroma/pleroma - needs: - - 'kaniko-release:linux/amd64' - - 'kaniko-release:linux/arm64' - - 'kaniko-release:linux/arm' script: - 'docker manifest create $IMAGE_TAG $IMAGES' - 'docker manifest push $IMAGE_TAG' diff --git a/changelog.d/3878.skip b/changelog.d/3878.skip new file mode 100644 index 000000000..e69de29bb From 675639225a905f5b0b2650cd3f20a4758fc3f868 Mon Sep 17 00:00:00 2001 From: HJ <30-hj@users.noreply.git.pleroma.social> Date: Fri, 28 Apr 2023 11:13:42 +0000 Subject: [PATCH 0166/1008] allow https: so that flash works across instances without need for media proxy --- lib/pleroma/web/plugs/http_security_plug.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index 34895c8d5..045384e08 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -104,7 +104,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do {[img_src, " https:"], [media_src, " https:"]} end - connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url] + connect_src = ["connect-src 'self' blob: https: ", static_url, ?\s, websocket_url] connect_src = if Config.get(:env) == :dev do From cd20d15bb8d2f97f8dd0850993041f15865cdda9 Mon Sep 17 00:00:00 2001 From: HJ <30-hj@users.noreply.git.pleroma.social> Date: Fri, 28 Apr 2023 11:19:14 +0000 Subject: [PATCH 0167/1008] changelog --- changelog.d/3879.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3879.fix diff --git a/changelog.d/3879.fix b/changelog.d/3879.fix new file mode 100644 index 000000000..7c58cc3c2 --- /dev/null +++ b/changelog.d/3879.fix @@ -0,0 +1 @@ +fix not being able to fetch flash file from remote instance \ No newline at end of file From 85bdbb102e8fe4dc4a0eb765d8a2a01308161772 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 4 Mar 2023 19:50:47 -0500 Subject: [PATCH 0168/1008] Add extraction process for oauth scopes --- lib/pleroma/web/api_spec/scopes/compiler.ex | 82 +++++++++++++++++++ lib/pleroma/web/api_spec/scopes/translator.ex | 10 +++ .../web/api_spec/scopes/compiler_test.exs | 56 +++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 lib/pleroma/web/api_spec/scopes/compiler.ex create mode 100644 lib/pleroma/web/api_spec/scopes/translator.ex create mode 100644 test/pleroma/web/api_spec/scopes/compiler_test.exs diff --git a/lib/pleroma/web/api_spec/scopes/compiler.ex b/lib/pleroma/web/api_spec/scopes/compiler.ex new file mode 100644 index 000000000..162edc9a3 --- /dev/null +++ b/lib/pleroma/web/api_spec/scopes/compiler.ex @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Scopes.Compiler do + defmacro __before_compile__(_env) do + strings = __MODULE__.extract_all_scopes() + + quote do + def placeholder do + unquote do + Enum.map( + strings, + fn string -> + quote do + Pleroma.Web.Gettext.dgettext_noop( + "oauth_scopes", + unquote(string) + ) + end + end + ) + end + end + end + end + + def extract_all_scopes do + extract_all_scopes_from(Pleroma.Web.ApiSpec.spec()) + end + + def extract_all_scopes_from(specs) do + specs.paths + |> Enum.reduce([], fn + {_path, %{} = path_item}, acc -> + extract_routes(path_item) + |> Enum.flat_map(fn operation -> process_operation(operation) end) + |> Kernel.++(acc) + + {_, _}, acc -> + acc + end) + |> Enum.uniq() + end + + defp extract_routes(path_item) do + path_item + |> Map.from_struct() + |> Enum.map(fn {_method, path_item} -> path_item end) + |> Enum.filter(fn + %OpenApiSpex.Operation{} = _operation -> true + _ -> false + end) + end + + defp process_operation(operation) do + operation.security + |> Kernel.||([]) + |> Enum.flat_map(fn + %{"oAuth" => scopes} -> process_scopes(scopes) + _ -> [] + end) + end + + defp process_scopes(scopes) do + scopes + |> Enum.flat_map(fn scope -> + process_scope(scope) + end) + end + + def process_scope(scope) do + hierarchy = String.split(scope, ":") + + {_, list} = + Enum.reduce(hierarchy, {"", []}, fn comp, {cur, list} -> + {cur <> comp <> ":", [cur <> comp | list]} + end) + + list + end +end diff --git a/lib/pleroma/web/api_spec/scopes/translator.ex b/lib/pleroma/web/api_spec/scopes/translator.ex new file mode 100644 index 000000000..54eea3593 --- /dev/null +++ b/lib/pleroma/web/api_spec/scopes/translator.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Scopes.Translator do + require Pleroma.Web.ApiSpec.Scopes.Compiler + require Pleroma.Web.Gettext + + @before_compile Pleroma.Web.ApiSpec.Scopes.Compiler +end diff --git a/test/pleroma/web/api_spec/scopes/compiler_test.exs b/test/pleroma/web/api_spec/scopes/compiler_test.exs new file mode 100644 index 000000000..99e1d343a --- /dev/null +++ b/test/pleroma/web/api_spec/scopes/compiler_test.exs @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Scopes.CompilerTest do + use ExUnit.Case, async: true + + alias Pleroma.Web.ApiSpec.Scopes.Compiler + + @dummy_response %{} + + @data %{ + paths: %{ + "/mew" => %OpenApiSpex.PathItem{ + post: %OpenApiSpex.Operation{ + security: [%{"oAuth" => ["a:b:c"]}], + responses: @dummy_response + }, + get: %OpenApiSpex.Operation{security: nil, responses: @dummy_response} + }, + "/mew2" => %OpenApiSpex.PathItem{ + post: %OpenApiSpex.Operation{ + security: [%{"oAuth" => ["d:e", "f:g"]}], + responses: @dummy_response + }, + get: %OpenApiSpex.Operation{security: nil, responses: @dummy_response} + } + } + } + + describe "process_scope/1" do + test "gives all higher-level scopes" do + scopes = Compiler.process_scope("admin:read:accounts") + + assert [_, _, _] = scopes + assert "admin" in scopes + assert "admin:read" in scopes + assert "admin:read:accounts" in scopes + end + end + + describe "extract_all_scopes_from/1" do + test "extracts scopes" do + scopes = Compiler.extract_all_scopes_from(@data) + + assert [_, _, _, _, _, _, _] = scopes + assert "a" in scopes + assert "a:b" in scopes + assert "a:b:c" in scopes + assert "d" in scopes + assert "d:e" in scopes + assert "f" in scopes + assert "f:g" in scopes + end + end +end From 530284e1b96b6daeb21995079ff21544e545aa7f Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 4 Mar 2023 19:57:42 -0500 Subject: [PATCH 0169/1008] Add extracted pot --- priv/gettext/oauth_scopes.pot | 261 ++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 priv/gettext/oauth_scopes.pot diff --git a/priv/gettext/oauth_scopes.pot b/priv/gettext/oauth_scopes.pot new file mode 100644 index 000000000..5f7b425f3 --- /dev/null +++ b/priv/gettext/oauth_scopes.pot @@ -0,0 +1,261 @@ +## This file is a PO Template file. +## +## "msgid"s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run "mix gettext.extract" to bring this file up to +## date. Leave "msgstr"s empty as changing them here has no +## effect: edit them in PO (.po) files instead. +msgid "" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "follow" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "push" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:accounts" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:backups" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:blocks" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:bookmarks" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:chats" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:favourites" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:filters" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:follows" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:lists" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:notifications" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:reports" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:search" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:statuses" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:accounts" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:blocks" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:bookmarks" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:chats" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:conversations" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:favourites" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:filters" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:follow" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:follows" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:lists" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:media" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:mutes" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:notifications" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:reports" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:statuses" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:accounts" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:chats" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:invites" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:media_proxy_caches" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:reports" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:statuses" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:accounts" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:chats" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:follows" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:invites" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:media_proxy_caches" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:reports" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:statuses" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:media" +msgstr "" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:mutes" +msgstr "" From 6d0ebccdb03f6d19bccb01a6c3ae4e7f647aa678 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 4 Mar 2023 19:58:05 -0500 Subject: [PATCH 0170/1008] Make webui use translated scope descriptions --- lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex index 73115e92a..7585c4d3e 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex @@ -8,7 +8,7 @@ <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> <%= label @form, :"scope_#{scope}", String.capitalize(scope) %> <%= if scope in @scopes && scope do %> - <%= String.capitalize(scope) %> + <%= scope %> <%= :"Elixir.Gettext".dgettext(Gettext, "oauth_scopes", scope) %> <% end %> <% else %> From b6dd194000a4a9ce23dc844758fa797f08a01550 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 2 May 2023 16:33:53 -0400 Subject: [PATCH 0171/1008] Add changelog --- changelog.d/3848.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3848.add diff --git a/changelog.d/3848.add b/changelog.d/3848.add new file mode 100644 index 000000000..d7b1b0a84 --- /dev/null +++ b/changelog.d/3848.add @@ -0,0 +1 @@ +Add OAuth scope descriptions From 9283c784a38194a937dc1a46a6b46478d159e434 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 2 May 2023 17:03:09 -0400 Subject: [PATCH 0172/1008] Add English translation for oauth scopes --- priv/gettext/en/LC_MESSAGES/oauth_scopes.po | 264 ++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 priv/gettext/en/LC_MESSAGES/oauth_scopes.po diff --git a/priv/gettext/en/LC_MESSAGES/oauth_scopes.po b/priv/gettext/en/LC_MESSAGES/oauth_scopes.po new file mode 100644 index 000000000..105ca022b --- /dev/null +++ b/priv/gettext/en/LC_MESSAGES/oauth_scopes.po @@ -0,0 +1,264 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Free Software Foundation, Inc. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"PO-Revision-Date: 2023-05-02 17:02-0400\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin" +msgstr "All admin access" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read" +msgstr "Read all using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write" +msgstr "Write all using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "follow" +msgstr "Read and write user relationships" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "push" +msgstr "Push notifications" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read" +msgstr "Read everything" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:accounts" +msgstr "Read information of all accounts" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:backups" +msgstr "Read your backups" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:blocks" +msgstr "Read block relationships" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:bookmarks" +msgstr "Read your bookmarks" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:chats" +msgstr "Read your chats" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:favourites" +msgstr "Read your favourites" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:filters" +msgstr "Read your filtering settings" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:follows" +msgstr "Read follow relationships" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:lists" +msgstr "Read your lists" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:notifications" +msgstr "Read your notifications" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:reports" +msgstr "Read your reports" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:search" +msgstr "Perform searches" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:statuses" +msgstr "Read all statuses you can see" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write" +msgstr "Write everything" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:accounts" +msgstr "Change your account information" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:blocks" +msgstr "Block or unblock someone" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:bookmarks" +msgstr "Add to or remove from your bookmarks" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:chats" +msgstr "Create or delete chats or chat messages, or mark them as read" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:conversations" +msgstr "Change recipients of, mark as read, or delete conversations" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:favourites" +msgstr "Favourite or unfavourite statuses" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:filters" +msgstr "Change your filtering settings" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:follow" +msgstr "Follow or unfollow someone" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:follows" +msgstr "Follow or unfollow someone" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:lists" +msgstr "Create, change or delete your lists" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:media" +msgstr "Upload media files or modify those you uploaded" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:mutes" +msgstr "Mute or unmute someone" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:notifications" +msgstr "Mark notifications as read" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:reports" +msgstr "Submit reports" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "write:statuses" +msgstr "Post, edit, reblog or react to statuses" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:accounts" +msgstr "Read all accounts using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:chats" +msgstr "Read all chats using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:invites" +msgstr "Read all invites using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:media_proxy_caches" +msgstr "Read media proxy caches using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:reports" +msgstr "Read all reports using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:read:statuses" +msgstr "Read all statuses using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:accounts" +msgstr "Change all accounts using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:chats" +msgstr "Change all chats using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:follows" +msgstr "Change follow relationships using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:invites" +msgstr "Invite or revoke an invite using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:media_proxy_caches" +msgstr "Change media proxy caches using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:reports" +msgstr "Handle reports using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "admin:write:statuses" +msgstr "Delete, change scope of, or mark as sensitive statuses using admin API" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:media" +msgstr "Read media attachments" + +#, elixir-autogen, elixir-format +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +msgid "read:mutes" +msgstr "Read mute relationships" From 99f157e2804327d4ad3f47b83638060f774a582b Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 2 May 2023 22:12:15 -0400 Subject: [PATCH 0173/1008] Fix MR pipelines not having build and test jobs --- .gitlab-ci.yml | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2eee3bc68..b70415ac6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,24 +56,27 @@ check-changelog: - apk add git - sh ./tools/check-changelog +.build_changes_policy: + rules: + - changes: + - ".gitlab-ci.yml" + - "**/*.ex" + - "**/*.exs" + - "mix.lock" + build: + extends: .build_changes_policy stage: build - only: - changes: &build_changes_policy - - ".gitlab-ci.yml" - - "**/*.ex" - - "**/*.exs" - - "mix.lock" script: - mix compile --force spec-build: stage: test - only: - changes: - - ".gitlab-ci.yml" - - "lib/pleroma/web/api_spec/**/*.ex" - - "lib/pleroma/web/api_spec.ex" + rules: + - changes: + - ".gitlab-ci.yml" + - "lib/pleroma/web/api_spec/**/*.ex" + - "lib/pleroma/web/api_spec.ex" artifacts: paths: - spec.json @@ -95,9 +98,8 @@ benchmark: - mix pleroma.load_testing unit-testing: + extends: .build_changes_policy stage: test - only: - changes: *build_changes_policy cache: &testing_cache_policy <<: *global_cache_policy policy: pull @@ -118,11 +120,10 @@ unit-testing: path: coverage.xml unit-testing-erratic: + extends: .build_changes_policy stage: test retry: 2 allow_failure: true - only: - changes: *build_changes_policy cache: &testing_cache_policy <<: *global_cache_policy policy: pull @@ -153,9 +154,8 @@ unit-testing-erratic: # - mix test --trace --only federated unit-testing-rum: + extends: .build_changes_policy stage: test - only: - changes: *build_changes_policy cache: *testing_cache_policy services: - name: minibikini/postgres-with-rum:12 @@ -171,10 +171,9 @@ unit-testing-rum: - mix test --preload-modules lint: + extends: .build_changes_policy image: ¤t_elixir elixir:1.12-alpine stage: test - only: - changes: *build_changes_policy cache: *testing_cache_policy before_script: ¤t_bfr_script - apk update @@ -186,18 +185,16 @@ lint: - mix format --check-formatted analysis: + extends: .build_changes_policy stage: test - only: - changes: *build_changes_policy cache: *testing_cache_policy script: - mix credo --strict --only=warnings,todo,fixme,consistency,readability cycles: + extends: .build_changes_policy image: *current_elixir stage: test - only: - changes: *build_changes_policy cache: {} before_script: *current_bfr_script script: From 6a3fd8e01404f7fc4415626300d81c65c045a7d6 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 2 May 2023 22:16:00 -0400 Subject: [PATCH 0174/1008] Do not count for renames when diffing --- tools/check-changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check-changelog b/tools/check-changelog index 1409be9f0..60692033f 100644 --- a/tools/check-changelog +++ b/tools/check-changelog @@ -5,7 +5,7 @@ echo "looking for change log" git remote add upstream https://git.pleroma.social/pleroma/pleroma.git git fetch upstream ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}:refs/remotes/upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME -git diff --raw upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \ +git diff --raw --no-renames upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \ grep ' A\t' | grep '\.\(skip\|add\|remove\|fix\|security\)$' ret=$? From 4fd96b24aef831095ac8b3494fe4c59d8c9de333 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 10:53:09 +0200 Subject: [PATCH 0175/1008] AddRemoveValidator: Use User.fetch_by_ap_id instead of upgrade_user_from_ap_id --- .../web/activity_pub/object_validators/add_remove_validator.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex index 5202db7f1..db3259550 100644 --- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex @@ -73,6 +73,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do end defp maybe_refetch_user(%User{ap_id: ap_id}) do - Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(ap_id) + # Maybe it could use User.get_or_fetch_by_ap_id to avoid refreshing too often + User.fetch_by_ap_id(ap_id) end end From 0903c416456d137fa40bef231c31f038c8c20e9f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 10:54:16 +0200 Subject: [PATCH 0176/1008] User: Stop relying on ap_enabled --- lib/pleroma/user.ex | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f6e30555c..2a0faed77 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1061,11 +1061,7 @@ defmodule Pleroma.User do end def maybe_direct_follow(%User{} = follower, %User{} = followed) do - if not ap_enabled?(followed) do - follow(follower, followed) - else - {:ok, follower, followed} - end + {:ok, follower, followed} end @doc "A mass follow for local users. Respects blocks in both directions but does not create activities." From 606f78f5e5781a59cb9d083e8085144cca326cc6 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 10:55:08 +0200 Subject: [PATCH 0177/1008] ActivityPub: Stop relying on ap_enabled and upgrade_user_from_ap_id --- lib/pleroma/web/activity_pub/activity_pub.ex | 28 +++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index f22756015..2fae98fad 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1751,24 +1751,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def make_user_from_ap_id(ap_id, additional \\ []) do user = User.get_cached_by_ap_id(ap_id) - if user && !User.ap_enabled?(user) do - Transmogrifier.upgrade_user_from_ap_id(ap_id) - else - with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do - {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end) + with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do + {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end) - if user do - user - |> User.remote_user_changeset(data) - |> User.update_and_set_cache() - else - maybe_handle_clashing_nickname(data) + if user do + user + |> User.remote_user_changeset(data) + |> User.update_and_set_cache() + else + maybe_handle_clashing_nickname(data) - data - |> User.remote_user_changeset() - |> Repo.insert() - |> User.set_cache() - end + data + |> User.remote_user_changeset() + |> Repo.insert() + |> User.set_cache() end end end From 3962253cf16d19237b00b5e01573aed10fb596bd Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 10:55:46 +0200 Subject: [PATCH 0178/1008] Publisher: Stop filtering via ap_enabled?/1 --- lib/pleroma/web/activity_pub/publisher.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 6c1ba76a3..af6aa0781 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -199,7 +199,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do inboxes = recipients - |> Enum.filter(&User.ap_enabled?/1) |> Enum.map(fn actor -> actor.inbox end) |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) |> Instances.filter_reachable() @@ -241,7 +240,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do json = Jason.encode!(data) recipients(actor, activity) - |> Enum.filter(fn user -> User.ap_enabled?(user) end) |> Enum.map(fn %User{} = user -> determine_inbox(activity, user) end) From 2ee483ba41eb4f01c3ca763c30dcaa07d3b258f9 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 11:00:40 +0200 Subject: [PATCH 0179/1008] Transmogrifier: Remove upgrade_user_from_ap_id --- .../web/activity_pub/transmogrifier.ex | 20 ------ .../web/activity_pub/transmogrifier_test.exs | 64 ------------------- 2 files changed, 84 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 3141f8437..ea7a6a810 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -20,7 +20,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator - alias Pleroma.Workers.TransmogrifierWorker import Ecto.Query @@ -968,25 +967,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Repo.update_all([]) end - def upgrade_user_from_ap_id(ap_id) do - with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id), - {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id), - {:ok, user} <- update_user(user, data) do - {:ok, _pid} = Task.start(fn -> ActivityPub.pinned_fetch_task(user) end) - TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id}) - {:ok, user} - else - %User{} = user -> {:ok, user} - e -> e - end - end - - defp update_user(user, data) do - user - |> User.remote_user_changeset(data) - |> User.update_and_set_cache() - end - def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do Map.put(data, "url", url["href"]) end diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 6b4636d22..588f46296 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do alias Pleroma.Activity alias Pleroma.Object - alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils @@ -339,69 +338,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do end end - describe "user upgrade" do - test "it upgrades a user to activitypub" do - user = - insert(:user, %{ - nickname: "rye@niu.moe", - local: false, - ap_id: "https://niu.moe/users/rye", - follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"}) - }) - - user_two = insert(:user) - Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept) - - {:ok, activity} = CommonAPI.post(user, %{status: "test"}) - {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"}) - assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients - - user = User.get_cached_by_id(user.id) - assert user.note_count == 1 - - {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye") - ObanHelpers.perform_all() - - assert user.ap_enabled - assert user.note_count == 1 - assert user.follower_address == "https://niu.moe/users/rye/followers" - assert user.following_address == "https://niu.moe/users/rye/following" - - user = User.get_cached_by_id(user.id) - assert user.note_count == 1 - - activity = Activity.get_by_id(activity.id) - assert user.follower_address in activity.recipients - - assert %{ - "url" => [ - %{ - "href" => - "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg" - } - ] - } = user.avatar - - assert %{ - "url" => [ - %{ - "href" => - "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png" - } - ] - } = user.banner - - refute "..." in activity.recipients - - unrelated_activity = Activity.get_by_id(unrelated_activity.id) - refute user.follower_address in unrelated_activity.recipients - - user_two = User.get_cached_by_id(user_two.id) - assert User.following?(user_two, user) - refute "..." in User.following(user_two) - end - end - describe "actor rewriting" do test "it fixes the actor URL property to be a proper URI" do data = %{ From e17265a7a248db7514b091d47ae79d1a2de5b440 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 11:01:29 +0200 Subject: [PATCH 0180/1008] TransmogrifierWorker: Remove obsolete worker --- .../web/activity_pub/transmogrifier.ex | 22 ------------------- lib/pleroma/workers/transmogrifier_worker.ex | 18 --------------- 2 files changed, 40 deletions(-) delete mode 100644 lib/pleroma/workers/transmogrifier_worker.ex diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index ea7a6a810..0e6c429f9 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -945,28 +945,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do defp strip_internal_tags(object), do: object - def perform(:user_upgrade, user) do - # we pass a fake user so that the followers collection is stripped away - old_follower_address = User.ap_followers(%User{nickname: user.nickname}) - - from( - a in Activity, - where: ^old_follower_address in a.recipients, - update: [ - set: [ - recipients: - fragment( - "array_replace(?,?,?)", - a.recipients, - ^old_follower_address, - ^user.follower_address - ) - ] - ] - ) - |> Repo.update_all([]) - end - def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do Map.put(data, "url", url["href"]) end diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex deleted file mode 100644 index 1f3f5385e..000000000 --- a/lib/pleroma/workers/transmogrifier_worker.ex +++ /dev/null @@ -1,18 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.TransmogrifierWorker do - alias Pleroma.User - - use Pleroma.Workers.WorkerHelper, queue: "transmogrifier" - - @impl Oban.Worker - 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 - - @impl Oban.Worker - def timeout(_job), do: :timer.seconds(5) -end From 8181be89a28ec293cb36fccc2324bc78cc118d3a Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 11:02:58 +0200 Subject: [PATCH 0181/1008] Federator: Stop using ap_enabled?/1 --- lib/pleroma/web/federator.ex | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 318b6cb11..84b77cda1 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Activity alias Pleroma.Object.Containment alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Federator.Publisher @@ -80,7 +79,7 @@ defmodule Pleroma.Web.Federator do # NOTE: we use the actor ID to do the containment, this is fine because an # actor shouldn't be acting on objects outside their own AP server. - with {_, {:ok, _user}} <- {:actor, ap_enabled_actor(actor)}, + with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)}, nil <- Activity.normalize(params["id"]), {_, :ok} <- {:correct_origin?, Containment.contain_origin_from_id(actor, params)}, @@ -110,14 +109,4 @@ defmodule Pleroma.Web.Federator do {:error, e} end end - - def ap_enabled_actor(id) do - user = User.get_cached_by_ap_id(id) - - if User.ap_enabled?(user) do - {:ok, user} - else - ActivityPub.make_user_from_ap_id(id) - end - end end From 9dfa1c4be0d9c37222a299c212607407f93d6596 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 11:07:18 +0200 Subject: [PATCH 0182/1008] ActivityPub: Mark fetch_and_prepare_user_from_ap_id/1 as private --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 2fae98fad..9175e9e13 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1668,7 +1668,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end - def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do + defp fetch_and_prepare_user_from_ap_id(ap_id, additional) do with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), {:ok, data} <- user_data_from_user_object(data, additional) do {:ok, maybe_update_follow_information(data)} From 238edc30dec2cb48b3a6eca48397d5202060726e Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 11:08:26 +0200 Subject: [PATCH 0183/1008] User: Remove ap_enabled?/1 --- lib/pleroma/user.ex | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 2a0faed77..eff82afd9 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2147,10 +2147,6 @@ defmodule Pleroma.User do end end - def ap_enabled?(%User{local: true}), do: true - def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled - def ap_enabled?(_), do: false - @doc "Gets or fetch a user by uri or nickname." @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()} def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri) From fcd49e3985d34bd3b783791e12e638f66cab0a9b Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 11:09:15 +0200 Subject: [PATCH 0184/1008] User: Remove ap_enabled field --- lib/pleroma/user.ex | 3 --- lib/pleroma/web/activity_pub/activity_pub.ex | 1 - .../20230504173400_remove_user_ap_enabled.exs | 13 +++++++++++++ test/pleroma/user_test.exs | 11 +++-------- .../activity_pub/activity_pub_controller_test.exs | 1 - .../web/activity_pub/activity_pub_test.exs | 1 - test/pleroma/web/activity_pub/publisher_test.exs | 15 +++++---------- test/pleroma/web/common_api_test.exs | 2 +- test/pleroma/web/federator_test.exs | 6 ++---- test/support/factory.ex | 1 - 10 files changed, 24 insertions(+), 30 deletions(-) create mode 100644 priv/repo/migrations/20230504173400_remove_user_ap_enabled.exs diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index eff82afd9..ce125d608 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -124,7 +124,6 @@ defmodule Pleroma.User do field(:domain_blocks, {:array, :string}, default: []) field(:is_active, :boolean, default: true) field(:no_rich_text, :boolean, default: false) - field(:ap_enabled, :boolean, default: false) field(:is_moderator, :boolean, default: false) field(:is_admin, :boolean, default: false) field(:show_role, :boolean, default: true) @@ -488,7 +487,6 @@ defmodule Pleroma.User do :nickname, :public_key, :avatar, - :ap_enabled, :banner, :is_locked, :last_refreshed_at, @@ -1894,7 +1892,6 @@ defmodule Pleroma.User do confirmation_token: nil, domain_blocks: [], is_active: false, - ap_enabled: false, is_moderator: false, is_admin: false, mascot: nil, diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 9175e9e13..1a10aef1d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1547,7 +1547,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do %{ ap_id: data["id"], uri: get_actor_url(data["url"]), - ap_enabled: true, banner: normalize_image(data["image"]), fields: fields, emoji: emojis, diff --git a/priv/repo/migrations/20230504173400_remove_user_ap_enabled.exs b/priv/repo/migrations/20230504173400_remove_user_ap_enabled.exs new file mode 100644 index 000000000..0aea41324 --- /dev/null +++ b/priv/repo/migrations/20230504173400_remove_user_ap_enabled.exs @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.RemoveUserApEnabled do + use Ecto.Migration + + def change do + alter table(:users) do + remove(:ap_enabled, :boolean, default: false, null: false) + end + end +end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index c16312a65..7f60b959a 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1844,7 +1844,6 @@ defmodule Pleroma.UserTest do confirmation_token: "qqqq", domain_blocks: ["lain.com"], is_active: false, - ap_enabled: true, is_moderator: true, is_admin: true, mascot: %{"a" => "b"}, @@ -1885,7 +1884,6 @@ defmodule Pleroma.UserTest do confirmation_token: nil, domain_blocks: [], is_active: false, - ap_enabled: false, is_moderator: false, is_admin: false, mascot: nil, @@ -2473,8 +2471,7 @@ defmodule Pleroma.UserTest do insert(:user, local: false, follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following", - ap_enabled: true + following_address: "http://localhost:4001/users/masto_closed/following" ) assert other_user.following_count == 0 @@ -2495,8 +2492,7 @@ defmodule Pleroma.UserTest do insert(:user, local: false, follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following", - ap_enabled: true + following_address: "http://localhost:4001/users/masto_closed/following" ) assert other_user.following_count == 0 @@ -2517,8 +2513,7 @@ defmodule Pleroma.UserTest do insert(:user, local: false, follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following", - ap_enabled: true + following_address: "http://localhost:4001/users/masto_closed/following" ) assert other_user.following_count == 0 diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index ef91066c1..62eb9b5a3 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -575,7 +575,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do user = insert(:user, ap_id: "https://mastodon.example.org/users/raymoo", - ap_enabled: true, local: false, last_refreshed_at: nil ) diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index a6f8b6152..54fc6ef0a 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -174,7 +174,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) assert user.ap_id == user_id assert user.nickname == "admin@mastodon.example.org" - assert user.ap_enabled assert user.follower_address == "http://mastodon.example.org/users/admin/followers" end diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index e2db3d575..c5137cbb7 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -276,8 +276,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do follower = insert(:user, %{ local: false, - inbox: "https://domain.com/users/nick1/inbox", - ap_enabled: true + inbox: "https://domain.com/users/nick1/inbox" }) actor = insert(:user, follower_address: follower.ap_id) @@ -313,8 +312,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do follower = insert(:user, %{ local: false, - inbox: "https://domain.com/users/nick1/inbox", - ap_enabled: true + inbox: "https://domain.com/users/nick1/inbox" }) actor = insert(:user, follower_address: follower.ap_id) @@ -348,8 +346,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do follower = insert(:user, %{ local: false, - inbox: "https://domain.com/users/nick1/inbox", - ap_enabled: true + inbox: "https://domain.com/users/nick1/inbox" }) actor = insert(:user, follower_address: follower.ap_id) @@ -382,15 +379,13 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do fetcher = insert(:user, local: false, - inbox: "https://domain.com/users/nick1/inbox", - ap_enabled: true + inbox: "https://domain.com/users/nick1/inbox" ) another_fetcher = insert(:user, local: false, - inbox: "https://domain2.com/users/nick1/inbox", - ap_enabled: true + inbox: "https://domain2.com/users/nick1/inbox" ) actor = insert(:user) diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 5c9103e9f..5bd0df0b2 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -1328,7 +1328,7 @@ defmodule Pleroma.Web.CommonAPITest do test "cancels a pending follow for a remote user" do follower = insert(:user) - followed = insert(:user, is_locked: true, local: false, ap_enabled: true) + followed = insert(:user, is_locked: true, local: false) assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(follower, followed) diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs index 1ffe6aae1..6826e6c2f 100644 --- a/test/pleroma/web/federator_test.exs +++ b/test/pleroma/web/federator_test.exs @@ -78,16 +78,14 @@ defmodule Pleroma.Web.FederatorTest do local: false, nickname: "nick1@domain.com", ap_id: "https://domain.com/users/nick1", - inbox: inbox1, - ap_enabled: true + inbox: inbox1 }) insert(:user, %{ local: false, nickname: "nick2@domain2.com", ap_id: "https://domain2.com/users/nick2", - inbox: inbox2, - ap_enabled: true + inbox: inbox2 }) dt = NaiveDateTime.utc_now() diff --git a/test/support/factory.ex b/test/support/factory.ex index 09f02458c..d94544717 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -50,7 +50,6 @@ defmodule Pleroma.Factory do last_refreshed_at: NaiveDateTime.utc_now(), notification_settings: %Pleroma.User.NotificationSetting{}, multi_factor_authentication_settings: %Pleroma.MFA.Settings{}, - ap_enabled: true, keys: pem } From c63bf6a040ffd982da43e980209a7d96e87e4d7f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 5 May 2023 11:13:50 +0200 Subject: [PATCH 0185/1008] Add changelog for !3880 --- changelog.d/3880.remove | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3880.remove diff --git a/changelog.d/3880.remove b/changelog.d/3880.remove new file mode 100644 index 000000000..113c76c85 --- /dev/null +++ b/changelog.d/3880.remove @@ -0,0 +1 @@ +Cleanup OStatus-era user upgrades and ap_enabled indicator \ No newline at end of file From c0d11da2d8edc57ef88163c06a19aad3e28d14db Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 7 May 2023 15:16:30 +0300 Subject: [PATCH 0186/1008] conditionally set csp depnding on media-proxy state --- lib/pleroma/web/plugs/http_security_plug.ex | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index 045384e08..df46cfa0c 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -93,18 +93,26 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do img_src = "img-src 'self' data: blob:" media_src = "media-src 'self'" + connect_src = ["connect-src 'self' blob:", static_url, ?\s, websocket_url] # Strict multimedia CSP enforcement only when MediaProxy is enabled - {img_src, media_src} = + {img_src, media_src, connect_src} = if Config.get([:media_proxy, :enabled]) && !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do sources = build_csp_multimedia_source_list() - {[img_src, sources], [media_src, sources]} + { + [img_src, sources], + [media_src, sources], + [connect_src, sources] + } else - {[img_src, " https:"], [media_src, " https:"]} + { + [img_src, " https:"], + [media_src, " https:"], + [connect_src, " https:"] + } end - connect_src = ["connect-src 'self' blob: https: ", static_url, ?\s, websocket_url] connect_src = if Config.get(:env) == :dev do From f8ef4924ecab5ba6851eee82845624bc15f868de Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 7 May 2023 15:24:09 +0300 Subject: [PATCH 0187/1008] fix whitespace --- lib/pleroma/web/plugs/http_security_plug.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index df46cfa0c..a3166bc96 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do img_src = "img-src 'self' data: blob:" media_src = "media-src 'self'" - connect_src = ["connect-src 'self' blob:", static_url, ?\s, websocket_url] + connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url] # Strict multimedia CSP enforcement only when MediaProxy is enabled {img_src, media_src, connect_src} = From f50fd9278fd36e6bd3ae36bb7f5033d9fd8a84ac Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 7 May 2023 15:29:19 +0300 Subject: [PATCH 0188/1008] reduce redundant reduntancy reduction --- lib/pleroma/web/plugs/http_security_plug.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index a3166bc96..b189d5bfd 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do img_src = "img-src 'self' data: blob:" media_src = "media-src 'self'" - connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url] + connect_src = "connect-src 'self' blob:" # Strict multimedia CSP enforcement only when MediaProxy is enabled {img_src, media_src, connect_src} = @@ -103,7 +103,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do { [img_src, sources], [media_src, sources], - [connect_src, sources] + [connect_src, sources, ?\s, websocket_url] } else { From 2a07411b0cb14ea26966659605d95074b02a8538 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 7 May 2023 15:34:17 +0300 Subject: [PATCH 0189/1008] keep the websocket url for all modes --- lib/pleroma/web/plugs/http_security_plug.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index b189d5bfd..b3dc8a3a6 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do img_src = "img-src 'self' data: blob:" media_src = "media-src 'self'" - connect_src = "connect-src 'self' blob:" + connect_src = ["connect-src 'self' blob: ", ?\s, websocket_url] # Strict multimedia CSP enforcement only when MediaProxy is enabled {img_src, media_src, connect_src} = @@ -103,7 +103,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do { [img_src, sources], [media_src, sources], - [connect_src, sources, ?\s, websocket_url] + [connect_src, sources] } else { From 163e82bab1cc12ad323bfc9c1fcada11fa3b223a Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 9 May 2023 19:25:39 -0400 Subject: [PATCH 0190/1008] Allow lang attribute --- changelog.d/3882.add | 1 + priv/scrubbers/default.ex | 77 ++++++++++++++-------------- test/pleroma/web/common_api_test.exs | 11 ++++ 3 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 changelog.d/3882.add diff --git a/changelog.d/3882.add b/changelog.d/3882.add new file mode 100644 index 000000000..4712de1dc --- /dev/null +++ b/changelog.d/3882.add @@ -0,0 +1 @@ +Allow lang attribute in status text diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index e10e3ec87..d1215d2e0 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -33,35 +33,35 @@ defmodule Pleroma.HTML.Scrubber.Default do "ugc" ]) - Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) + Meta.allow_tag_with_these_attributes(:a, ["name", "title", "lang"]) - Meta.allow_tag_with_these_attributes(:abbr, ["title"]) + Meta.allow_tag_with_these_attributes(:abbr, ["title", "lang"]) - Meta.allow_tag_with_these_attributes(:b, []) - Meta.allow_tag_with_these_attributes(:blockquote, []) - Meta.allow_tag_with_these_attributes(:br, []) - Meta.allow_tag_with_these_attributes(:code, []) - Meta.allow_tag_with_these_attributes(:del, []) - Meta.allow_tag_with_these_attributes(:em, []) - Meta.allow_tag_with_these_attributes(:hr, []) - Meta.allow_tag_with_these_attributes(:i, []) - Meta.allow_tag_with_these_attributes(:li, []) - Meta.allow_tag_with_these_attributes(:ol, []) - Meta.allow_tag_with_these_attributes(:p, []) - Meta.allow_tag_with_these_attributes(:pre, []) - Meta.allow_tag_with_these_attributes(:strong, []) - Meta.allow_tag_with_these_attributes(:sub, []) - Meta.allow_tag_with_these_attributes(:sup, []) - Meta.allow_tag_with_these_attributes(:ruby, []) - Meta.allow_tag_with_these_attributes(:rb, []) - Meta.allow_tag_with_these_attributes(:rp, []) - Meta.allow_tag_with_these_attributes(:rt, []) - Meta.allow_tag_with_these_attributes(:rtc, []) - Meta.allow_tag_with_these_attributes(:u, []) - Meta.allow_tag_with_these_attributes(:ul, []) + Meta.allow_tag_with_these_attributes(:b, ["lang"]) + Meta.allow_tag_with_these_attributes(:blockquote, ["lang"]) + Meta.allow_tag_with_these_attributes(:br, ["lang"]) + Meta.allow_tag_with_these_attributes(:code, ["lang"]) + Meta.allow_tag_with_these_attributes(:del, ["lang"]) + Meta.allow_tag_with_these_attributes(:em, ["lang"]) + Meta.allow_tag_with_these_attributes(:hr, ["lang"]) + Meta.allow_tag_with_these_attributes(:i, ["lang"]) + Meta.allow_tag_with_these_attributes(:li, ["lang"]) + Meta.allow_tag_with_these_attributes(:ol, ["lang"]) + Meta.allow_tag_with_these_attributes(:p, ["lang"]) + Meta.allow_tag_with_these_attributes(:pre, ["lang"]) + Meta.allow_tag_with_these_attributes(:strong, ["lang"]) + Meta.allow_tag_with_these_attributes(:sub, ["lang"]) + Meta.allow_tag_with_these_attributes(:sup, ["lang"]) + Meta.allow_tag_with_these_attributes(:ruby, ["lang"]) + Meta.allow_tag_with_these_attributes(:rb, ["lang"]) + Meta.allow_tag_with_these_attributes(:rp, ["lang"]) + Meta.allow_tag_with_these_attributes(:rt, ["lang"]) + Meta.allow_tag_with_these_attributes(:rtc, ["lang"]) + Meta.allow_tag_with_these_attributes(:u, ["lang"]) + Meta.allow_tag_with_these_attributes(:ul, ["lang"]) Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "recipients-inline"]) - Meta.allow_tag_with_these_attributes(:span, []) + Meta.allow_tag_with_these_attributes(:span, ["lang"]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"]) @@ -77,29 +77,30 @@ defmodule Pleroma.HTML.Scrubber.Default do "width", "height", "title", - "alt" + "alt", + "lang" ]) end if Pleroma.Config.get([:markup, :allow_tables]) do - Meta.allow_tag_with_these_attributes(:table, []) - Meta.allow_tag_with_these_attributes(:tbody, []) - Meta.allow_tag_with_these_attributes(:td, []) - Meta.allow_tag_with_these_attributes(:th, []) - Meta.allow_tag_with_these_attributes(:thead, []) - Meta.allow_tag_with_these_attributes(:tr, []) + Meta.allow_tag_with_these_attributes(:table, ["lang"]) + Meta.allow_tag_with_these_attributes(:tbody, ["lang"]) + Meta.allow_tag_with_these_attributes(:td, ["lang"]) + Meta.allow_tag_with_these_attributes(:th, ["lang"]) + Meta.allow_tag_with_these_attributes(:thead, ["lang"]) + Meta.allow_tag_with_these_attributes(:tr, ["lang"]) end if Pleroma.Config.get([:markup, :allow_headings]) do - Meta.allow_tag_with_these_attributes(:h1, []) - Meta.allow_tag_with_these_attributes(:h2, []) - Meta.allow_tag_with_these_attributes(:h3, []) - Meta.allow_tag_with_these_attributes(:h4, []) - Meta.allow_tag_with_these_attributes(:h5, []) + Meta.allow_tag_with_these_attributes(:h1, ["lang"]) + Meta.allow_tag_with_these_attributes(:h2, ["lang"]) + Meta.allow_tag_with_these_attributes(:h3, ["lang"]) + Meta.allow_tag_with_these_attributes(:h4, ["lang"]) + Meta.allow_tag_with_these_attributes(:h5, ["lang"]) end if Pleroma.Config.get([:markup, :allow_fonts]) do - Meta.allow_tag_with_these_attributes(:font, ["face"]) + Meta.allow_tag_with_these_attributes(:font, ["face", "lang"]) end Meta.strip_everything_not_covered() diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 5c9103e9f..968d826a2 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -527,6 +527,17 @@ defmodule Pleroma.Web.CommonAPITest do assert Object.tags(object) == ["ساٴين‌س"] end + test "allows lang attribute" do + user = insert(:user) + text = ~s{something

random

} + + {:ok, activity} = CommonAPI.post(user, %{status: text, content_type: "text/html"}) + + object = Object.normalize(activity, fetch: false) + + assert object.data["content"] == text + end + test "double dot in link is allowed" do user = insert(:user) text = "https://example.to/something..mp3" From be5c5118cb296792d82c007fe3b83d8795dca4b0 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 9 May 2023 21:04:01 -0400 Subject: [PATCH 0191/1008] Make sure object refetching follows update rules --- changelog.d/3883.fix | 1 + lib/pleroma/object/fetcher.ex | 86 ++++---------------- lib/pleroma/object/updater.ex | 49 +++++++++++ lib/pleroma/web/activity_pub/side_effects.ex | 36 ++------ test/pleroma/object/fetcher_test.exs | 84 ++++++++++++++++--- 5 files changed, 145 insertions(+), 111 deletions(-) create mode 100644 changelog.d/3883.fix diff --git a/changelog.d/3883.fix b/changelog.d/3883.fix new file mode 100644 index 000000000..6824f2013 --- /dev/null +++ b/changelog.d/3883.fix @@ -0,0 +1 @@ +Fix abnormal behaviour when refetching a poll diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index a9a9eeeed..cc3772563 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -8,77 +8,30 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment - alias Pleroma.Repo alias Pleroma.Signature alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator require Logger require Pleroma.Constants - defp touch_changeset(changeset) do - updated_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.truncate(:second) - - Ecto.Changeset.put_change(changeset, :updated_at, updated_at) - end - - defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do - has_history? = fn - %{"formerRepresentations" => %{"orderedItems" => list}} when is_list(list) -> true - _ -> false - end - - internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields()) - - remote_history_exists? = has_history?.(new_data) - - # If the remote history exists, we treat that as the only source of truth. - new_data = - if has_history?.(old_data) and not remote_history_exists? do - Map.put(new_data, "formerRepresentations", old_data["formerRepresentations"]) - else - new_data - end - - # If the remote does not have history information, we need to manage it ourselves - new_data = - if not remote_history_exists? do - changed? = - Pleroma.Constants.status_updatable_fields() - |> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end) - - %{updated_object: updated_object} = - new_data - |> Object.Updater.maybe_update_history(old_data, - updated: changed?, - use_history_in_new_object?: false - ) - - updated_object - else - new_data - end - - Map.merge(new_data, internal_fields) - end - - defp maybe_reinject_internal_fields(_, new_data), do: new_data - @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()} - defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do + defp reinject_object(%Object{data: %{}} = object, new_data) do Logger.debug("Reinjecting object #{new_data["id"]}") - with data <- maybe_reinject_internal_fields(object, new_data), - {:ok, data, _} <- ObjectValidator.validate(data, %{}), - changeset <- Object.change(object, %{data: data}), - changeset <- touch_changeset(changeset), - {:ok, object} <- Repo.insert_or_update(changeset), - {:ok, object} <- Object.set_cache(object) do - {:ok, object} + with {:ok, new_data, _} <- ObjectValidator.validate(new_data, %{}), + {:ok, new_data} <- MRF.filter(new_data), + {:ok, new_object, _} <- + Object.Updater.do_update_and_invalidate_cache( + object, + new_data, + _touch_changeset? = true + ) do + {:ok, new_object} else e -> Logger.error("Error while processing object: #{inspect(e)}") @@ -86,20 +39,11 @@ defmodule Pleroma.Object.Fetcher do end end - defp reinject_object(%Object{} = object, new_data) do - Logger.debug("Reinjecting object #{new_data["id"]}") - - with new_data <- Transmogrifier.fix_object(new_data), - data <- maybe_reinject_internal_fields(object, new_data), - changeset <- Object.change(object, %{data: data}), - changeset <- touch_changeset(changeset), - {:ok, object} <- Repo.insert_or_update(changeset), - {:ok, object} <- Object.set_cache(object) do + defp reinject_object(_, new_data) do + with {:ok, object, _} <- Pipeline.common_pipeline(new_data, local: false) do {:ok, object} else - e -> - Logger.error("Error while processing object: #{inspect(e)}") - {:error, e} + e -> e end end diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex index ab38d3ed2..0c523ed35 100644 --- a/lib/pleroma/object/updater.ex +++ b/lib/pleroma/object/updater.ex @@ -5,6 +5,9 @@ defmodule Pleroma.Object.Updater do require Pleroma.Constants + alias Pleroma.Object + alias Pleroma.Repo + def update_content_fields(orig_object_data, updated_object) do Pleroma.Constants.status_updatable_fields() |> Enum.reduce( @@ -237,4 +240,50 @@ defmodule Pleroma.Object.Updater do {:history_items, e} -> e end end + + defp maybe_touch_changeset(changeset, true) do + updated_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.truncate(:second) + + Ecto.Changeset.put_change(changeset, :updated_at, updated_at) + end + + defp maybe_touch_changeset(changeset, _), do: changeset + + def do_update_and_invalidate_cache(orig_object, updated_object, touch_changeset? \\ false) do + orig_object_ap_id = updated_object["id"] + orig_object = Object.get_by_ap_id(orig_object_ap_id) + orig_object_data = orig_object.data + + %{ + updated_data: updated_object_data, + updated: updated, + used_history_in_new_object?: used_history_in_new_object? + } = make_new_object_data_from_update_object(orig_object_data, updated_object) + + changeset = + orig_object + |> Repo.preload(:hashtags) + |> Object.change(%{data: updated_object_data}) + |> maybe_touch_changeset(touch_changeset?) + + with {:ok, new_object} <- Repo.update(changeset), + {:ok, _} <- Object.invalid_object_cache(new_object), + {:ok, _} <- Object.set_cache(new_object), + # The metadata/utils.ex uses the object id for the cache. + {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do + if used_history_in_new_object? do + with create_activity when not is_nil(create_activity) <- + Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id), + {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do + nil + else + _ -> nil + end + end + + {:ok, new_object, updated} + end + end end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index e19642d50..098c177c7 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -428,37 +428,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end if orig_object_data["type"] in Pleroma.Constants.updatable_object_types() do - %{ - updated_data: updated_object_data, - updated: updated, - used_history_in_new_object?: used_history_in_new_object? - } = Object.Updater.make_new_object_data_from_update_object(orig_object_data, updated_object) + {:ok, _, updated} = + Object.Updater.do_update_and_invalidate_cache(orig_object, updated_object) - changeset = - orig_object - |> Repo.preload(:hashtags) - |> Object.change(%{data: updated_object_data}) - - with {:ok, new_object} <- Repo.update(changeset), - {:ok, _} <- Object.invalid_object_cache(new_object), - {:ok, _} <- Object.set_cache(new_object), - # The metadata/utils.ex uses the object id for the cache. - {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do - if used_history_in_new_object? do - with create_activity when not is_nil(create_activity) <- - Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id), - {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do - nil - else - _ -> nil - end - end - - if updated do - object - |> Activity.normalize() - |> ActivityPub.notify_and_stream() - end + if updated do + object + |> Activity.normalize() + |> ActivityPub.notify_and_stream() end end diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index c8ad66ddb..53c9277d6 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -9,8 +9,12 @@ defmodule Pleroma.Object.FetcherTest do alias Pleroma.Instances alias Pleroma.Object alias Pleroma.Object.Fetcher + alias Pleroma.Web.ActivityPub.ObjectValidator + + require Pleroma.Constants import Mock + import Pleroma.Factory import Tesla.Mock setup do @@ -284,6 +288,8 @@ defmodule Pleroma.Object.FetcherTest do describe "refetching" do setup do + insert(:user, ap_id: "https://mastodon.social/users/emelie") + object1 = %{ "id" => "https://mastodon.social/1", "actor" => "https://mastodon.social/users/emelie", @@ -293,10 +299,14 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], - "summary" => "" + "to" => [Pleroma.Constants.as_public()], + "summary" => "", + "published" => "2023-05-08 23:43:20Z", + "updated" => "2023-05-09 23:43:20Z" } + {:ok, local_object1, _} = ObjectValidator.validate(object1, []) + object2 = %{ "id" => "https://mastodon.social/2", "actor" => "https://mastodon.social/users/emelie", @@ -306,8 +316,10 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], + "to" => [Pleroma.Constants.as_public()], "summary" => "", + "published" => "2023-05-08 23:43:20Z", + "updated" => "2023-05-09 23:43:25Z", "formerRepresentations" => %{ "type" => "OrderedCollection", "orderedItems" => [ @@ -319,14 +331,18 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], - "summary" => "" + "to" => [Pleroma.Constants.as_public()], + "summary" => "", + "published" => "2023-05-08 23:43:20Z", + "updated" => "2023-05-09 23:43:21Z" } ], "totalItems" => 1 } } + {:ok, local_object2, _} = ObjectValidator.validate(object2, []) + mock(fn %{ method: :get, @@ -335,7 +351,7 @@ defmodule Pleroma.Object.FetcherTest do %Tesla.Env{ status: 200, headers: [{"content-type", "application/activity+json"}], - body: Jason.encode!(object1) + body: Jason.encode!(object1 |> Map.put("updated", "2023-05-09 23:44:20Z")) } %{ @@ -345,7 +361,7 @@ defmodule Pleroma.Object.FetcherTest do %Tesla.Env{ status: 200, headers: [{"content-type", "application/activity+json"}], - body: Jason.encode!(object2) + body: Jason.encode!(object2 |> Map.put("updated", "2023-05-09 23:44:20Z")) } %{ @@ -370,7 +386,7 @@ defmodule Pleroma.Object.FetcherTest do apply(HttpRequestMock, :request, [env]) end) - %{object1: object1, object2: object2} + %{object1: local_object1, object2: local_object2} end test "it keeps formerRepresentations if remote does not have this attr", %{object1: object1} do @@ -388,8 +404,9 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], - "summary" => "" + "to" => [Pleroma.Constants.as_public()], + "summary" => "", + "published" => "2023-05-08 23:43:20Z" } ], "totalItems" => 1 @@ -467,6 +484,53 @@ defmodule Pleroma.Object.FetcherTest do } } = refetched.data end + + test "it keeps the history intact if only updated time has changed", + %{object1: object1} do + full_object1 = + object1 + |> Map.merge(%{ + "updated" => "2023-05-08 23:43:47Z", + "formerRepresentations" => %{ + "type" => "OrderedCollection", + "orderedItems" => [ + %{"type" => "Note", "content" => "mew mew 1"} + ], + "totalItems" => 1 + } + }) + + {:ok, o} = Object.create(full_object1) + + assert {:ok, refetched} = Fetcher.refetch_object(o) + + assert %{ + "content" => "test 1", + "formerRepresentations" => %{ + "orderedItems" => [ + %{"content" => "mew mew 1"} + ], + "totalItems" => 1 + } + } = refetched.data + end + + test "it goes through ObjectValidator and MRF", %{object2: object2} do + with_mock Pleroma.Web.ActivityPub.MRF, [:passthrough], + filter: fn + %{"type" => "Note"} = object -> + {:ok, Map.put(object, "content", "MRFd content")} + + arg -> + passthrough([arg]) + end do + {:ok, o} = Object.create(object2) + + assert {:ok, refetched} = Fetcher.refetch_object(o) + + assert %{"content" => "MRFd content"} = refetched.data + end + end end describe "fetch with history" do From e170fc40dd46b2cfa4970c5d8174bdcfd6cf821c Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 9 May 2023 21:38:28 -0400 Subject: [PATCH 0192/1008] Fix build warning --- lib/pleroma/object/updater.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex index 0c523ed35..bad811965 100644 --- a/lib/pleroma/object/updater.ex +++ b/lib/pleroma/object/updater.ex @@ -253,7 +253,6 @@ defmodule Pleroma.Object.Updater do def do_update_and_invalidate_cache(orig_object, updated_object, touch_changeset? \\ false) do orig_object_ap_id = updated_object["id"] - orig_object = Object.get_by_ap_id(orig_object_ap_id) orig_object_data = orig_object.data %{ From 9363ef53a34c9d96191bccaece76dd4e01f493b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 14 May 2023 15:02:58 +0200 Subject: [PATCH 0193/1008] Add test for 'status' notification type for NotificationView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../views/notification_view_test.exs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index 6ea894691..92de6c6a7 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -286,4 +286,31 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do test_notifications_rendering([notification], user, [expected]) end + + test "Subscribed status notification" do + user = insert(:user) + subscriber = insert(:user) + + User.subscribe(subscriber, user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hi"}) + {:ok, [notification]} = Notification.create_notifications(activity) + + user = User.get_cached_by_id(user.id) + + expected = %{ + id: to_string(notification.id), + pleroma: %{is_seen: false, is_muted: false}, + type: "status", + account: + AccountView.render("show.json", %{ + user: user, + for: subscriber + }), + status: StatusView.render("show.json", %{activity: activity, for: subscriber}), + created_at: Utils.to_masto_date(notification.inserted_at) + } + + test_notifications_rendering([notification], subscriber, [expected]) + end end From fb3335ffe287205274167d73a3124d2f811f2b6b Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 17 May 2023 17:12:21 +0200 Subject: [PATCH 0194/1008] EctoType: Add BareUri --- .../object_validators/bare_uri.ex | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 lib/pleroma/ecto_type/activity_pub/object_validators/bare_uri.ex diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/bare_uri.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/bare_uri.ex new file mode 100644 index 000000000..1038296e7 --- /dev/null +++ b/lib/pleroma/ecto_type/activity_pub/object_validators/bare_uri.ex @@ -0,0 +1,23 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.BareUri do + use Ecto.Type + + def type, do: :string + + def cast(uri) when is_binary(uri) do + case URI.parse(uri) do + %URI{scheme: nil} -> :error + %URI{} -> {:ok, uri} + _ -> :error + end + end + + def cast(_), do: :error + + def dump(data), do: {:ok, data} + + def load(data), do: {:ok, data} +end From a5066bb0789e15d808e99e8676c16ad74290419c Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 17 May 2023 17:13:26 +0200 Subject: [PATCH 0195/1008] CommonFields: Use BareUri for :url Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3121 --- changelog.d/3884.fix | 1 + .../object_validators/common_fields.ex | 2 +- .../object_validators/bare_uri_test.ex | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 changelog.d/3884.fix create mode 100644 test/pleroma/ecto_type/activity_pub/object_validators/bare_uri_test.ex diff --git a/changelog.d/3884.fix b/changelog.d/3884.fix new file mode 100644 index 000000000..f8dbb2bbf --- /dev/null +++ b/changelog.d/3884.fix @@ -0,0 +1 @@ +Allow non-HTTP(s) URIs in "url" fields for compatibility with "FEP-fffd: Proxy Objects" \ No newline at end of file diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index 7b60c139a..d580208df 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -58,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do field(:like_count, :integer, default: 0) field(:announcement_count, :integer, default: 0) field(:inReplyTo, ObjectValidators.ObjectID) - field(:url, ObjectValidators.Uri) + field(:url, ObjectValidators.BareUri) field(:likes, {:array, ObjectValidators.ObjectID}, default: []) field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/bare_uri_test.ex b/test/pleroma/ecto_type/activity_pub/object_validators/bare_uri_test.ex new file mode 100644 index 000000000..226383c3c --- /dev/null +++ b/test/pleroma/ecto_type/activity_pub/object_validators/bare_uri_test.ex @@ -0,0 +1,25 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.BareUriTest do + use Pleroma.DataCase, async: true + + alias Pleroma.EctoType.ActivityPub.ObjectValidators.BareUri + + test "diaspora://" do + text = "diaspora://alice@fediverse.example/post/deadbeefdeadbeefdeadbeefdeadbeef" + assert {:ok, text} = BareUri.cast(text) + end + + test "nostr:" do + text = "nostr:note1gwdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + assert {:ok, text} = BareUri.cast(text) + end + + test "errors for non-URIs" do + assert :error == SafeText.cast(1) + assert :error == SafeText.cast("foo") + assert :error == SafeText.cast("foo bar") + end +end From 505e58d4eb84226613c85ac526c591dab5b1307e Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 22 May 2023 08:14:20 -0400 Subject: [PATCH 0196/1008] Fix ObjectTest --- lib/pleroma/object/updater.ex | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex index bad811965..269609f1b 100644 --- a/lib/pleroma/object/updater.ex +++ b/lib/pleroma/object/updater.ex @@ -100,12 +100,14 @@ defmodule Pleroma.Object.Updater do end defp maybe_update_poll(to_be_updated, updated_object) do - choice_key = fn data -> - if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf" + choice_key = fn + %{"anyOf" => [_ | _]} = data -> "anyOf" + %{"oneOf" => [_ | _]} = data -> "oneOf" + _ -> nil end with true <- to_be_updated["type"] == "Question", - key <- choice_key.(updated_object), + key when not is_nil(key) <- choice_key.(updated_object), true <- key == choice_key.(to_be_updated), orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])), new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])), From 6aafa7fe7661bcf9e62c16381ffeb2d32ec9bafa Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 22 May 2023 08:16:14 -0400 Subject: [PATCH 0197/1008] Add changelog --- changelog.d/fix-object-test.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/fix-object-test.fix diff --git a/changelog.d/fix-object-test.fix b/changelog.d/fix-object-test.fix new file mode 100644 index 000000000..5eea719f0 --- /dev/null +++ b/changelog.d/fix-object-test.fix @@ -0,0 +1 @@ +Correctly handle the situation when a poll has both "anyOf" and "oneOf" but one of them being empty From 819a82da99588b53a67f42d8572a2484c4f17d40 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 22 May 2023 08:19:58 -0400 Subject: [PATCH 0198/1008] Fix unused variable --- lib/pleroma/object/updater.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex index 269609f1b..b1e4870ba 100644 --- a/lib/pleroma/object/updater.ex +++ b/lib/pleroma/object/updater.ex @@ -101,8 +101,8 @@ defmodule Pleroma.Object.Updater do defp maybe_update_poll(to_be_updated, updated_object) do choice_key = fn - %{"anyOf" => [_ | _]} = data -> "anyOf" - %{"oneOf" => [_ | _]} = data -> "oneOf" + %{"anyOf" => [_ | _]} -> "anyOf" + %{"oneOf" => [_ | _]} -> "oneOf" _ -> nil end From 2c66f584b53efe834e359b6829f5a73ad067dce2 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 21 May 2023 09:11:43 -0400 Subject: [PATCH 0199/1008] Show more informative errors when profile exceeds char limits --- .../update-credentials-limit-error.fix | 1 + .../controllers/account_controller.ex | 12 +++++ .../mastodon_api/update_credentials_test.exs | 48 ++++++++++++++++--- 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 changelog.d/update-credentials-limit-error.fix diff --git a/changelog.d/update-credentials-limit-error.fix b/changelog.d/update-credentials-limit-error.fix new file mode 100644 index 000000000..7682f958e --- /dev/null +++ b/changelog.d/update-credentials-limit-error.fix @@ -0,0 +1 @@ +Show more informative errors when profile exceeds char limits diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index c313a0e97..9a4b56301 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -263,6 +263,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:error, %Ecto.Changeset{errors: [background: {"file is too large", _}]}} -> render_error(conn, :request_entity_too_large, "File is too large") + {:error, %Ecto.Changeset{errors: [{:bio, {_, _}} | _]}} -> + render_error(conn, :request_entity_too_large, "Bio is too long") + + {:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} -> + render_error(conn, :request_entity_too_large, "Name is too long") + + {:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} -> + render_error(conn, :request_entity_too_large, "One or more field entries are too long") + + {:error, %Ecto.Changeset{errors: [{:fields, {_, _}} | _]}} -> + render_error(conn, :request_entity_too_large, "Too many field entries") + _e -> render_error(conn, :forbidden, "Invalid request") end diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index 6c63d53c2..45412bb34 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -97,6 +97,42 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do assert user.raw_bio == raw_bio end + test "updating bio honours bio limit", %{conn: conn} do + bio_limit = Config.get([:instance, :user_bio_length], 5000) + + raw_bio = String.duplicate(".", bio_limit + 1) + + conn = patch(conn, "/api/v1/accounts/update_credentials", %{"note" => raw_bio}) + + assert %{"error" => "Bio is too long"} = json_response_and_validate_schema(conn, 413) + end + + test "updating name honours name limit", %{conn: conn} do + name_limit = Config.get([:instance, :user_name_length], 100) + + name = String.duplicate(".", name_limit + 1) + + conn = patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => name}) + + assert %{"error" => "Name is too long"} = json_response_and_validate_schema(conn, 413) + end + + test "when both name and bio exceeds the limit, display name error", %{conn: conn} do + name_limit = Config.get([:instance, :user_name_length], 100) + bio_limit = Config.get([:instance, :user_bio_length], 5000) + + name = String.duplicate(".", name_limit + 1) + raw_bio = String.duplicate(".", bio_limit + 1) + + conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "display_name" => name, + "note" => raw_bio + }) + + assert %{"error" => "Name is too long"} = json_response_and_validate_schema(conn, 413) + end + test "updates the user's locking status", %{conn: conn} do conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"}) @@ -595,17 +631,17 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do fields = [%{"name" => "foo", "value" => long_value}] - assert %{"error" => "Invalid request"} == + assert %{"error" => "One or more field entries are too long"} == conn |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) - |> json_response_and_validate_schema(403) + |> json_response_and_validate_schema(413) fields = [%{"name" => long_name, "value" => "bar"}] - assert %{"error" => "Invalid request"} == + assert %{"error" => "One or more field entries are too long"} == conn |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) - |> json_response_and_validate_schema(403) + |> json_response_and_validate_schema(413) clear_config([:instance, :max_account_fields], 1) @@ -614,10 +650,10 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do %{"name" => "link", "value" => "cofe.io"} ] - assert %{"error" => "Invalid request"} == + assert %{"error" => "Too many field entries"} == conn |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) - |> json_response_and_validate_schema(403) + |> json_response_and_validate_schema(413) end end From 1fa196d8f7abfeccfcd911c74190866ad0950ca0 Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 25 May 2023 18:40:38 -0400 Subject: [PATCH 0200/1008] Fix deleting banned users' statuses --- changelog.d/delete-status-of-banned-user.fix | 1 + lib/pleroma/web/common_api.ex | 2 +- test/pleroma/web/common_api_test.exs | 14 +++++++++++++ .../controllers/status_controller_test.exs | 21 +++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 changelog.d/delete-status-of-banned-user.fix diff --git a/changelog.d/delete-status-of-banned-user.fix b/changelog.d/delete-status-of-banned-user.fix new file mode 100644 index 000000000..1fa6a29d8 --- /dev/null +++ b/changelog.d/delete-status-of-banned-user.fix @@ -0,0 +1 @@ +Fix error 404 when deleting status of a banned user diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 89cc0d6fe..65d08de49 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -142,7 +142,7 @@ defmodule Pleroma.Web.CommonAPI do def delete(activity_id, user) do with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <- - {:find_activity, Activity.get_by_id(activity_id)}, + {:find_activity, Activity.get_by_id(activity_id, filter: [])}, {_, %Object{} = object, _} <- {:find_object, Object.normalize(activity, fetch: false), activity}, true <- User.privileged?(user, :messages_delete) || user.ap_id == object.data["actor"], diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 968d826a2..9f4739a1f 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -393,6 +393,20 @@ defmodule Pleroma.Web.CommonAPITest do refute Activity.get_by_id(post.id) end + + test "it allows privileged users to delete banned user's posts" do + clear_config([:instance, :moderator_privileges], [:messages_delete]) + user = insert(:user) + moderator = insert(:user, is_moderator: true) + + {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"}) + User.set_activation(user, false) + + assert {:ok, delete} = CommonAPI.delete(post.id, moderator) + assert delete.local + + refute Activity.get_by_id(post.id) + end end test "favoriting race condition" do diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 1e8979127..4f434cb69 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -1018,6 +1018,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do refute Activity.get_by_id(activity.id) end + + test "when you're privileged and the user is banned", %{conn: conn} do + clear_config([:instance, :moderator_privileges], [:messages_delete]) + posting_user = insert(:user, is_active: false) + refute posting_user.is_active + activity = insert(:note_activity, user: posting_user) + user = insert(:user, is_moderator: true) + + res_conn = + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"])) + |> delete("/api/v1/statuses/#{activity.id}") + + assert %{} = json_response_and_validate_schema(res_conn, 200) + + assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() == + "@#{user.nickname} deleted status ##{activity.id}" + + refute Activity.get_by_id(activity.id) + end end describe "reblogging" do From 279fd47b486ccfda4537d7a64d553ac261a6fdd8 Mon Sep 17 00:00:00 2001 From: Zero Date: Thu, 25 May 2023 12:36:05 -0400 Subject: [PATCH 0201/1008] ForceMentionsInContent: fix double mentions for Mastodon/Misskey posts The code checked for duplicates using "ap_id", but in Mastodon and Misskey the look like that: Mastodon: https://mastodon.example.com/users/roger Misskey: https:///misskey.example.com/users/104ab42f11 The fix is to also check for "uri", which is what will be in the "explicitly_mentioned_uris" list: Mastodon: https://mastodon.example.com/@roger Misskey: https://misskey.example.com/@roger --- changelog.d/3888.fix | 1 + .../mrf/force_mentions_in_content.ex | 6 +-- .../mrf/force_mentions_in_content_test.exs | 51 +++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 changelog.d/3888.fix diff --git a/changelog.d/3888.fix b/changelog.d/3888.fix new file mode 100644 index 000000000..886aa7b39 --- /dev/null +++ b/changelog.d/3888.fix @@ -0,0 +1 @@ +ForceMentionsInContent: fix double mentions for Mastodon/Misskey posts \ No newline at end of file diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex index 70224561c..b9b175cc3 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors +# Copyright © 2017-2023 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do @@ -98,8 +98,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do explicitly_mentioned_uris = extract_mention_uris_from_content(content) added_mentions = - Enum.reduce(mention_users, "", fn %User{ap_id: uri} = user, acc -> - unless uri in explicitly_mentioned_uris do + Enum.reduce(mention_users, "", fn %User{ap_id: api_id, uri: uri} = user, acc -> + unless Enum.any?([api_id, uri], fn u -> u in explicitly_mentioned_uris end) do acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " " else acc diff --git a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs index b349a4bb7..5ebd972a6 100644 --- a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs +++ b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs @@ -256,4 +256,55 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do } }} = MRF.filter_one(ForceMentionsInContent, activity) end + + test "don't add duplicate mentions for mastodon or misskey posts" do + [zero, rogerick, greg] = [ + insert(:user, + ap_id: "https://pleroma.example.com/users/zero", + uri: "https://pleroma.example.com/users/zero", + nickname: "zero@pleroma.example.com", + local: false + ), + insert(:user, + ap_id: "https://misskey.example.com/users/104ab42f11", + uri: "https://misskey.example.com/@rogerick", + nickname: "rogerick@misskey.example.com", + local: false + ), + insert(:user, + ap_id: "https://mastodon.example.com/users/greg", + uri: "https://mastodon.example.com/@greg", + nickname: "greg@mastodon.example.com", + local: false + ) + ] + + {:ok, post} = CommonAPI.post(rogerick, %{status: "eugh"}) + + inline_mentions = [ + "@rogerick", + "@greg" + ] + + activity = %{ + "type" => "Create", + "actor" => zero.ap_id, + "object" => %{ + "type" => "Note", + "actor" => zero.ap_id, + "content" => "#{Enum.at(inline_mentions, 0)} #{Enum.at(inline_mentions, 1)} erm", + "to" => [ + rogerick.ap_id, + greg.ap_id, + Constants.as_public() + ], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + {:ok, %{"object" => %{"content" => filtered}}} = ForceMentionsInContent.filter(activity) + + assert filtered == + "#{Enum.at(inline_mentions, 0)} #{Enum.at(inline_mentions, 1)} erm" + end end From 38bcf6b19e3d83cb6c4e6c82d237a26edcab167a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 26 May 2023 12:08:50 -0400 Subject: [PATCH 0202/1008] MediaProxyController: Apply CSP sandbox --- changelog.d/3126.fix | 1 + .../web/media_proxy/media_proxy_controller.ex | 7 +++++++ .../media_proxy/media_proxy_controller_test.exs | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 changelog.d/3126.fix diff --git a/changelog.d/3126.fix b/changelog.d/3126.fix new file mode 100644 index 000000000..91d396c89 --- /dev/null +++ b/changelog.d/3126.fix @@ -0,0 +1 @@ +MediaProxy responses now return a sandbox CSP header diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index d2ad62c13..bda5b36ed 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Web.MediaProxy alias Plug.Conn + plug(:sandbox) + def remote(conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), @@ -202,4 +204,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do defp media_proxy_opts do Config.get([:media_proxy, :proxy_opts], []) end + + defp sandbox(conn, _params) do + conn + |> merge_resp_headers([{"content-security-policy", "sandbox;"}]) + end end diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index 5246bf0c4..9ce092fd8 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -6,7 +6,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do use Pleroma.Web.ConnCase import Mock + import Mox + alias Pleroma.ReverseProxy.ClientMock alias Pleroma.Web.MediaProxy alias Plug.Conn @@ -74,6 +76,20 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url) end end + + test "it applies sandbox CSP to MediaProxy requests", %{conn: conn} do + media_url = "https://lain.com/image.png" + media_proxy_url = MediaProxy.encode_url(media_url) + + ClientMock + |> expect(:request, fn :get, ^media_url, _, _, _ -> + {:ok, 200, [{"content-type", "image/png"}]} + end) + + %Conn{resp_headers: headers} = get(conn, media_proxy_url) + + assert {"content-security-policy", "sandbox;"} in headers + end end describe "Media Preview Proxy" do From 72833c84b583057ed0e757f35b65f90af40496df Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Wed, 17 May 2023 18:50:35 +0000 Subject: [PATCH 0203/1008] Merge branch 'tusooa/rework-refetch' into 'develop' Make sure object refetching follows update rules See merge request pleroma/pleroma!3883 --- changelog.d/3883.fix | 1 + lib/pleroma/object/fetcher.ex | 86 ++++---------------- lib/pleroma/object/updater.ex | 48 +++++++++++ lib/pleroma/web/activity_pub/side_effects.ex | 36 ++------ test/pleroma/object/fetcher_test.exs | 84 ++++++++++++++++--- 5 files changed, 144 insertions(+), 111 deletions(-) create mode 100644 changelog.d/3883.fix diff --git a/changelog.d/3883.fix b/changelog.d/3883.fix new file mode 100644 index 000000000..6824f2013 --- /dev/null +++ b/changelog.d/3883.fix @@ -0,0 +1 @@ +Fix abnormal behaviour when refetching a poll diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index a9a9eeeed..cc3772563 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -8,77 +8,30 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment - alias Pleroma.Repo alias Pleroma.Signature alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator require Logger require Pleroma.Constants - defp touch_changeset(changeset) do - updated_at = - NaiveDateTime.utc_now() - |> NaiveDateTime.truncate(:second) - - Ecto.Changeset.put_change(changeset, :updated_at, updated_at) - end - - defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do - has_history? = fn - %{"formerRepresentations" => %{"orderedItems" => list}} when is_list(list) -> true - _ -> false - end - - internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields()) - - remote_history_exists? = has_history?.(new_data) - - # If the remote history exists, we treat that as the only source of truth. - new_data = - if has_history?.(old_data) and not remote_history_exists? do - Map.put(new_data, "formerRepresentations", old_data["formerRepresentations"]) - else - new_data - end - - # If the remote does not have history information, we need to manage it ourselves - new_data = - if not remote_history_exists? do - changed? = - Pleroma.Constants.status_updatable_fields() - |> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end) - - %{updated_object: updated_object} = - new_data - |> Object.Updater.maybe_update_history(old_data, - updated: changed?, - use_history_in_new_object?: false - ) - - updated_object - else - new_data - end - - Map.merge(new_data, internal_fields) - end - - defp maybe_reinject_internal_fields(_, new_data), do: new_data - @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()} - defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do + defp reinject_object(%Object{data: %{}} = object, new_data) do Logger.debug("Reinjecting object #{new_data["id"]}") - with data <- maybe_reinject_internal_fields(object, new_data), - {:ok, data, _} <- ObjectValidator.validate(data, %{}), - changeset <- Object.change(object, %{data: data}), - changeset <- touch_changeset(changeset), - {:ok, object} <- Repo.insert_or_update(changeset), - {:ok, object} <- Object.set_cache(object) do - {:ok, object} + with {:ok, new_data, _} <- ObjectValidator.validate(new_data, %{}), + {:ok, new_data} <- MRF.filter(new_data), + {:ok, new_object, _} <- + Object.Updater.do_update_and_invalidate_cache( + object, + new_data, + _touch_changeset? = true + ) do + {:ok, new_object} else e -> Logger.error("Error while processing object: #{inspect(e)}") @@ -86,20 +39,11 @@ defmodule Pleroma.Object.Fetcher do end end - defp reinject_object(%Object{} = object, new_data) do - Logger.debug("Reinjecting object #{new_data["id"]}") - - with new_data <- Transmogrifier.fix_object(new_data), - data <- maybe_reinject_internal_fields(object, new_data), - changeset <- Object.change(object, %{data: data}), - changeset <- touch_changeset(changeset), - {:ok, object} <- Repo.insert_or_update(changeset), - {:ok, object} <- Object.set_cache(object) do + defp reinject_object(_, new_data) do + with {:ok, object, _} <- Pipeline.common_pipeline(new_data, local: false) do {:ok, object} else - e -> - Logger.error("Error while processing object: #{inspect(e)}") - {:error, e} + e -> e end end diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex index ab38d3ed2..bad811965 100644 --- a/lib/pleroma/object/updater.ex +++ b/lib/pleroma/object/updater.ex @@ -5,6 +5,9 @@ defmodule Pleroma.Object.Updater do require Pleroma.Constants + alias Pleroma.Object + alias Pleroma.Repo + def update_content_fields(orig_object_data, updated_object) do Pleroma.Constants.status_updatable_fields() |> Enum.reduce( @@ -237,4 +240,49 @@ defmodule Pleroma.Object.Updater do {:history_items, e} -> e end end + + defp maybe_touch_changeset(changeset, true) do + updated_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.truncate(:second) + + Ecto.Changeset.put_change(changeset, :updated_at, updated_at) + end + + defp maybe_touch_changeset(changeset, _), do: changeset + + def do_update_and_invalidate_cache(orig_object, updated_object, touch_changeset? \\ false) do + orig_object_ap_id = updated_object["id"] + orig_object_data = orig_object.data + + %{ + updated_data: updated_object_data, + updated: updated, + used_history_in_new_object?: used_history_in_new_object? + } = make_new_object_data_from_update_object(orig_object_data, updated_object) + + changeset = + orig_object + |> Repo.preload(:hashtags) + |> Object.change(%{data: updated_object_data}) + |> maybe_touch_changeset(touch_changeset?) + + with {:ok, new_object} <- Repo.update(changeset), + {:ok, _} <- Object.invalid_object_cache(new_object), + {:ok, _} <- Object.set_cache(new_object), + # The metadata/utils.ex uses the object id for the cache. + {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do + if used_history_in_new_object? do + with create_activity when not is_nil(create_activity) <- + Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id), + {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do + nil + else + _ -> nil + end + end + + {:ok, new_object, updated} + end + end end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index a2152b945..fc5dec362 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -428,37 +428,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end if orig_object_data["type"] in Pleroma.Constants.updatable_object_types() do - %{ - updated_data: updated_object_data, - updated: updated, - used_history_in_new_object?: used_history_in_new_object? - } = Object.Updater.make_new_object_data_from_update_object(orig_object_data, updated_object) + {:ok, _, updated} = + Object.Updater.do_update_and_invalidate_cache(orig_object, updated_object) - changeset = - orig_object - |> Repo.preload(:hashtags) - |> Object.change(%{data: updated_object_data}) - - with {:ok, new_object} <- Repo.update(changeset), - {:ok, _} <- Object.invalid_object_cache(new_object), - {:ok, _} <- Object.set_cache(new_object), - # The metadata/utils.ex uses the object id for the cache. - {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(new_object.id) do - if used_history_in_new_object? do - with create_activity when not is_nil(create_activity) <- - Pleroma.Activity.get_create_by_object_ap_id(orig_object_ap_id), - {:ok, _} <- Pleroma.Activity.HTML.invalidate_cache_for(create_activity.id) do - nil - else - _ -> nil - end - end - - if updated do - object - |> Activity.normalize() - |> ActivityPub.notify_and_stream() - end + if updated do + object + |> Activity.normalize() + |> ActivityPub.notify_and_stream() end end diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index c8ad66ddb..53c9277d6 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -9,8 +9,12 @@ defmodule Pleroma.Object.FetcherTest do alias Pleroma.Instances alias Pleroma.Object alias Pleroma.Object.Fetcher + alias Pleroma.Web.ActivityPub.ObjectValidator + + require Pleroma.Constants import Mock + import Pleroma.Factory import Tesla.Mock setup do @@ -284,6 +288,8 @@ defmodule Pleroma.Object.FetcherTest do describe "refetching" do setup do + insert(:user, ap_id: "https://mastodon.social/users/emelie") + object1 = %{ "id" => "https://mastodon.social/1", "actor" => "https://mastodon.social/users/emelie", @@ -293,10 +299,14 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], - "summary" => "" + "to" => [Pleroma.Constants.as_public()], + "summary" => "", + "published" => "2023-05-08 23:43:20Z", + "updated" => "2023-05-09 23:43:20Z" } + {:ok, local_object1, _} = ObjectValidator.validate(object1, []) + object2 = %{ "id" => "https://mastodon.social/2", "actor" => "https://mastodon.social/users/emelie", @@ -306,8 +316,10 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], + "to" => [Pleroma.Constants.as_public()], "summary" => "", + "published" => "2023-05-08 23:43:20Z", + "updated" => "2023-05-09 23:43:25Z", "formerRepresentations" => %{ "type" => "OrderedCollection", "orderedItems" => [ @@ -319,14 +331,18 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], - "summary" => "" + "to" => [Pleroma.Constants.as_public()], + "summary" => "", + "published" => "2023-05-08 23:43:20Z", + "updated" => "2023-05-09 23:43:21Z" } ], "totalItems" => 1 } } + {:ok, local_object2, _} = ObjectValidator.validate(object2, []) + mock(fn %{ method: :get, @@ -335,7 +351,7 @@ defmodule Pleroma.Object.FetcherTest do %Tesla.Env{ status: 200, headers: [{"content-type", "application/activity+json"}], - body: Jason.encode!(object1) + body: Jason.encode!(object1 |> Map.put("updated", "2023-05-09 23:44:20Z")) } %{ @@ -345,7 +361,7 @@ defmodule Pleroma.Object.FetcherTest do %Tesla.Env{ status: 200, headers: [{"content-type", "application/activity+json"}], - body: Jason.encode!(object2) + body: Jason.encode!(object2 |> Map.put("updated", "2023-05-09 23:44:20Z")) } %{ @@ -370,7 +386,7 @@ defmodule Pleroma.Object.FetcherTest do apply(HttpRequestMock, :request, [env]) end) - %{object1: object1, object2: object2} + %{object1: local_object1, object2: local_object2} end test "it keeps formerRepresentations if remote does not have this attr", %{object1: object1} do @@ -388,8 +404,9 @@ defmodule Pleroma.Object.FetcherTest do "bcc" => [], "bto" => [], "cc" => [], - "to" => [], - "summary" => "" + "to" => [Pleroma.Constants.as_public()], + "summary" => "", + "published" => "2023-05-08 23:43:20Z" } ], "totalItems" => 1 @@ -467,6 +484,53 @@ defmodule Pleroma.Object.FetcherTest do } } = refetched.data end + + test "it keeps the history intact if only updated time has changed", + %{object1: object1} do + full_object1 = + object1 + |> Map.merge(%{ + "updated" => "2023-05-08 23:43:47Z", + "formerRepresentations" => %{ + "type" => "OrderedCollection", + "orderedItems" => [ + %{"type" => "Note", "content" => "mew mew 1"} + ], + "totalItems" => 1 + } + }) + + {:ok, o} = Object.create(full_object1) + + assert {:ok, refetched} = Fetcher.refetch_object(o) + + assert %{ + "content" => "test 1", + "formerRepresentations" => %{ + "orderedItems" => [ + %{"content" => "mew mew 1"} + ], + "totalItems" => 1 + } + } = refetched.data + end + + test "it goes through ObjectValidator and MRF", %{object2: object2} do + with_mock Pleroma.Web.ActivityPub.MRF, [:passthrough], + filter: fn + %{"type" => "Note"} = object -> + {:ok, Map.put(object, "content", "MRFd content")} + + arg -> + passthrough([arg]) + end do + {:ok, o} = Object.create(object2) + + assert {:ok, refetched} = Fetcher.refetch_object(o) + + assert %{"content" => "MRFd content"} = refetched.data + end + end end describe "fetch with history" do From 4339230f64b05fee1c4d7313c1dc9adc45827a5d Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Tue, 23 May 2023 01:57:07 +0000 Subject: [PATCH 0204/1008] Merge branch 'tusooa/fix-object-test' into 'develop' Fix ObjectTest See merge request pleroma/pleroma!3887 --- changelog.d/fix-object-test.fix | 1 + lib/pleroma/object/updater.ex | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 changelog.d/fix-object-test.fix diff --git a/changelog.d/fix-object-test.fix b/changelog.d/fix-object-test.fix new file mode 100644 index 000000000..5eea719f0 --- /dev/null +++ b/changelog.d/fix-object-test.fix @@ -0,0 +1 @@ +Correctly handle the situation when a poll has both "anyOf" and "oneOf" but one of them being empty diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex index bad811965..b1e4870ba 100644 --- a/lib/pleroma/object/updater.ex +++ b/lib/pleroma/object/updater.ex @@ -100,12 +100,14 @@ defmodule Pleroma.Object.Updater do end defp maybe_update_poll(to_be_updated, updated_object) do - choice_key = fn data -> - if Map.has_key?(data, "anyOf"), do: "anyOf", else: "oneOf" + choice_key = fn + %{"anyOf" => [_ | _]} -> "anyOf" + %{"oneOf" => [_ | _]} -> "oneOf" + _ -> nil end with true <- to_be_updated["type"] == "Question", - key <- choice_key.(updated_object), + key when not is_nil(key) <- choice_key.(updated_object), true <- key == choice_key.(to_be_updated), orig_choices <- to_be_updated[key] |> Enum.map(&Map.drop(&1, ["replies"])), new_choices <- updated_object[key] |> Enum.map(&Map.drop(&1, ["replies"])), From b36263e5ffd0d89d819b01478f19891b14740bb0 Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Fri, 26 May 2023 17:12:18 +0000 Subject: [PATCH 0205/1008] Merge branch 'issue/3126' into 'develop' MediaProxyController: Apply CSP sandbox See merge request pleroma/pleroma!3890 --- changelog.d/3126.fix | 1 + .../web/media_proxy/media_proxy_controller.ex | 7 +++++++ .../media_proxy/media_proxy_controller_test.exs | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 changelog.d/3126.fix diff --git a/changelog.d/3126.fix b/changelog.d/3126.fix new file mode 100644 index 000000000..91d396c89 --- /dev/null +++ b/changelog.d/3126.fix @@ -0,0 +1 @@ +MediaProxy responses now return a sandbox CSP header diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index d2ad62c13..bda5b36ed 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Web.MediaProxy alias Plug.Conn + plug(:sandbox) + def remote(conn, %{"sig" => sig64, "url" => url64}) do with {_, true} <- {:enabled, MediaProxy.enabled?()}, {:ok, url} <- MediaProxy.decode_url(sig64, url64), @@ -202,4 +204,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do defp media_proxy_opts do Config.get([:media_proxy, :proxy_opts], []) end + + defp sandbox(conn, _params) do + conn + |> merge_resp_headers([{"content-security-policy", "sandbox;"}]) + end end diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index 5246bf0c4..9ce092fd8 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -6,7 +6,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do use Pleroma.Web.ConnCase import Mock + import Mox + alias Pleroma.ReverseProxy.ClientMock alias Pleroma.Web.MediaProxy alias Plug.Conn @@ -74,6 +76,20 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do assert %Conn{status: 404, resp_body: "Not Found"} = get(conn, url) end end + + test "it applies sandbox CSP to MediaProxy requests", %{conn: conn} do + media_url = "https://lain.com/image.png" + media_proxy_url = MediaProxy.encode_url(media_url) + + ClientMock + |> expect(:request, fn :get, ^media_url, _, _, _ -> + {:ok, 200, [{"content-type", "image/png"}]} + end) + + %Conn{resp_headers: headers} = get(conn, media_proxy_url) + + assert {"content-security-policy", "sandbox;"} in headers + end end describe "Media Preview Proxy" do From d0c2e0830b16c541d2883004f071a0954be45682 Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 30 Mar 2023 21:01:37 -0400 Subject: [PATCH 0206/1008] Enforce unauth restrictions for public streaming endpoints --- lib/pleroma/web/streamer.ex | 46 +++++++++++++++--- test/pleroma/web/streamer_test.exs | 77 ++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 3c0da5c27..b9a04cc76 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -25,6 +25,7 @@ defmodule Pleroma.Web.Streamer do def registry, do: @registry @public_streams ["public", "public:local", "public:media", "public:local:media"] + @local_streams ["public:local", "public:local:media"] @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"] @doc "Expands and authorizes a stream, and registers the process for streaming." @@ -41,14 +42,37 @@ defmodule Pleroma.Web.Streamer do end end + defp can_access_stream(user, oauth_token, kind) do + with {_, true} <- {:restrict?, Config.restrict_unauthenticated_access?(:timelines, kind)}, + {_, %User{id: user_id}, %Token{user_id: user_id}} <- {:user, user, oauth_token}, + {_, true} <- + {:scopes, + OAuthScopesPlug.filter_descendants(["read:statuses"], oauth_token.scopes) != []} do + true + else + {:restrict?, _} -> + true + + _ -> + false + end + end + @doc "Expand and authorizes a stream" @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) :: {:ok, topic :: String.t()} | {:error, :bad_topic} def get_topic(stream, user, oauth_token, params \\ %{}) - # Allow all public steams. - def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do - {:ok, stream} + # Allow all public steams if the instance allows unauthenticated access. + # Otherwise, only allow users with valid oauth tokens. + def get_topic(stream, user, oauth_token, _params) when stream in @public_streams do + kind = if stream in @local_streams, do: :local, else: :federated + + if can_access_stream(user, oauth_token, kind) do + {:ok, stream} + else + {:error, :unauthorized} + end end # Allow all hashtags streams. @@ -57,12 +81,20 @@ defmodule Pleroma.Web.Streamer do end # Allow remote instance streams. - def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do - {:ok, "public:remote:" <> instance} + def get_topic("public:remote", user, oauth_token, %{"instance" => instance} = _params) do + if can_access_stream(user, oauth_token, :federated) do + {:ok, "public:remote:" <> instance} + else + {:error, :unauthorized} + end end - def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do - {:ok, "public:remote:media:" <> instance} + def get_topic("public:remote:media", user, oauth_token, %{"instance" => instance} = _params) do + if can_access_stream(user, oauth_token, :federated) do + {:ok, "public:remote:media:" <> instance} + else + {:error, :unauthorized} + end end # Expand user streams. diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index 8b0c84164..7ab0e379b 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -29,6 +29,26 @@ defmodule Pleroma.Web.StreamerTest do assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil) end + test "rejects local public streams if restricted_unauthenticated is on" do + clear_config([:restrict_unauthenticated, :timelines, :local], true) + + assert {:error, :unauthorized} = Streamer.get_topic("public:local", nil, nil) + assert {:error, :unauthorized} = Streamer.get_topic("public:local:media", nil, nil) + end + + test "rejects remote public streams if restricted_unauthenticated is on" do + clear_config([:restrict_unauthenticated, :timelines, :federated], true) + + assert {:error, :unauthorized} = Streamer.get_topic("public", nil, nil) + assert {:error, :unauthorized} = Streamer.get_topic("public:media", nil, nil) + + assert {:error, :unauthorized} = + Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"}) + + assert {:error, :unauthorized} = + Streamer.get_topic("public:remote:media", nil, nil, %{"instance" => "lain.com"}) + end + test "allows instance streams" do assert {:ok, "public:remote:lain.com"} = Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"}) @@ -69,6 +89,63 @@ defmodule Pleroma.Web.StreamerTest do end end + test "allows local public streams if restricted_unauthenticated is on", %{ + user: user, + token: oauth_token + } do + clear_config([:restrict_unauthenticated, :timelines, :local], true) + + %{token: read_notifications_token} = oauth_access(["read:notifications"], user: user) + %{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user) + + assert {:ok, "public:local"} = Streamer.get_topic("public:local", user, oauth_token) + + assert {:ok, "public:local:media"} = + Streamer.get_topic("public:local:media", user, oauth_token) + + for token <- [read_notifications_token, badly_scoped_token] do + assert {:error, :unauthorized} = Streamer.get_topic("public:local", user, token) + + assert {:error, :unauthorized} = Streamer.get_topic("public:local:media", user, token) + end + end + + test "allows remote public streams if restricted_unauthenticated is on", %{ + user: user, + token: oauth_token + } do + clear_config([:restrict_unauthenticated, :timelines, :federated], true) + + %{token: read_notifications_token} = oauth_access(["read:notifications"], user: user) + %{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user) + + assert {:ok, "public"} = Streamer.get_topic("public", user, oauth_token) + assert {:ok, "public:media"} = Streamer.get_topic("public:media", user, oauth_token) + + assert {:ok, "public:remote:lain.com"} = + Streamer.get_topic("public:remote", user, oauth_token, %{"instance" => "lain.com"}) + + assert {:ok, "public:remote:media:lain.com"} = + Streamer.get_topic("public:remote:media", user, oauth_token, %{ + "instance" => "lain.com" + }) + + for token <- [read_notifications_token, badly_scoped_token] do + assert {:error, :unauthorized} = Streamer.get_topic("public", user, token) + assert {:error, :unauthorized} = Streamer.get_topic("public:media", user, token) + + assert {:error, :unauthorized} = + Streamer.get_topic("public:remote", user, token, %{ + "instance" => "lain.com" + }) + + assert {:error, :unauthorized} = + Streamer.get_topic("public:remote:media", user, token, %{ + "instance" => "lain.com" + }) + end + end + test "allows user streams (with proper OAuth token scopes)", %{ user: user, token: read_oauth_token From 0d68804aa7efc4f3212e02218804755da93d03f0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 26 May 2023 13:23:59 -0400 Subject: [PATCH 0207/1008] Filter OEmbed HTML tags --- changelog.d/3891.fix | 1 + lib/pleroma/web/rich_media/parsers/o_embed.ex | 4 ++-- test/pleroma/web/rich_media/parser_test.exs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 changelog.d/3891.fix diff --git a/changelog.d/3891.fix b/changelog.d/3891.fix new file mode 100644 index 000000000..f1fb62d82 --- /dev/null +++ b/changelog.d/3891.fix @@ -0,0 +1 @@ +OEmbed HTML tags are now filtered diff --git a/lib/pleroma/web/rich_media/parsers/o_embed.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex index 75318d9c7..0f303176c 100644 --- a/lib/pleroma/web/rich_media/parsers/o_embed.ex +++ b/lib/pleroma/web/rich_media/parsers/o_embed.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do def parse(html, _data) do with elements = [_ | _] <- get_discovery_data(html), oembed_url when is_binary(oembed_url) <- get_oembed_url(elements), - {:ok, oembed_data} <- get_oembed_data(oembed_url) do - oembed_data + {:ok, oembed_data = %{"html" => html}} <- get_oembed_data(oembed_url) do + %{oembed_data | "html" => Pleroma.HTML.filter_tags(html)} else _e -> %{} end diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs index ffdc4e5d7..9064138a6 100644 --- a/test/pleroma/web/rich_media/parser_test.exs +++ b/test/pleroma/web/rich_media/parser_test.exs @@ -129,7 +129,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do }} end - test "parses OEmbed" do + test "parses OEmbed and filters HTML tags" do assert Parser.parse("http://example.com/oembed") == {:ok, %{ @@ -139,7 +139,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "flickr_type" => "photo", "height" => "768", "html" => - "\"Bacon", + "\"Bacon", "license" => "All Rights Reserved", "license_id" => 0, "provider_name" => "Flickr", From 4505bc1e587ce76683e21a41f90fb2956ed2a084 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 26 May 2023 13:23:59 -0400 Subject: [PATCH 0208/1008] Filter OEmbed HTML tags --- changelog.d/3891.fix | 1 + lib/pleroma/web/rich_media/parsers/o_embed.ex | 4 ++-- test/pleroma/web/rich_media/parser_test.exs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 changelog.d/3891.fix diff --git a/changelog.d/3891.fix b/changelog.d/3891.fix new file mode 100644 index 000000000..f1fb62d82 --- /dev/null +++ b/changelog.d/3891.fix @@ -0,0 +1 @@ +OEmbed HTML tags are now filtered diff --git a/lib/pleroma/web/rich_media/parsers/o_embed.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex index 75318d9c7..0f303176c 100644 --- a/lib/pleroma/web/rich_media/parsers/o_embed.ex +++ b/lib/pleroma/web/rich_media/parsers/o_embed.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do def parse(html, _data) do with elements = [_ | _] <- get_discovery_data(html), oembed_url when is_binary(oembed_url) <- get_oembed_url(elements), - {:ok, oembed_data} <- get_oembed_data(oembed_url) do - oembed_data + {:ok, oembed_data = %{"html" => html}} <- get_oembed_data(oembed_url) do + %{oembed_data | "html" => Pleroma.HTML.filter_tags(html)} else _e -> %{} end diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs index ffdc4e5d7..9064138a6 100644 --- a/test/pleroma/web/rich_media/parser_test.exs +++ b/test/pleroma/web/rich_media/parser_test.exs @@ -129,7 +129,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do }} end - test "parses OEmbed" do + test "parses OEmbed and filters HTML tags" do assert Parser.parse("http://example.com/oembed") == {:ok, %{ @@ -139,7 +139,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "flickr_type" => "photo", "height" => "768", "html" => - "\"Bacon", + "\"Bacon", "license" => "All Rights Reserved", "license_id" => 0, "provider_name" => "Flickr", From 7618e562b34d4240514d8819407dd6bd3adbe79d Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 30 Mar 2023 12:52:57 +0200 Subject: [PATCH 0209/1008] Version 2.5.2 --- CHANGELOG.md | 19 +++++++++++++++++++ mix.exs | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1742ee2f4..f6fc6aaee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed +### Removed + +## 2.5.2 + +### Security +- `/proxy` endpoint now sets a Content-Security-Policy (sandbox) +- WebSocket endpoint now respects unauthenticated restrictions for streams of public posts +- OEmbed HTML tags are now filtered + +### Changed +- docs: Be more explicit about the level of compatibility of OTP releases +- Set default background worker timeout to 15 minutes + +### Fixed +- Atom/RSS formatting (HTML truncation, published, missing summary) +- Remove `static_fe` pipeline for `/users/:nickname/feed` +- Stop oban from retrying if validating errors occur when processing incoming data +- Make sure object refetching as used by already received polls follows MRF rules + ### Removed - BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) diff --git a/mix.exs b/mix.exs index 7439bbca6..79fd9c9ef 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.5.1"), + version: version("2.5.2"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), From f970091c6a58d06a42594e2c4a0baa5a86617652 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 26 May 2023 17:17:13 -0400 Subject: [PATCH 0210/1008] Add instructions to serve media on another domain --- docs/configuration/hardening.md | 14 ++++ installation/pleroma-mediaproxy.nginx | 97 +++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 installation/pleroma-mediaproxy.nginx diff --git a/docs/configuration/hardening.md b/docs/configuration/hardening.md index d3bfc4e4a..4f40873e6 100644 --- a/docs/configuration/hardening.md +++ b/docs/configuration/hardening.md @@ -62,6 +62,20 @@ An additional “Expect-CT” header will be sent with the configured `ct_max_ag If you click on a link, your browser’s request to the other site will include from where it is coming from. The “Referrer policy” header tells the browser how and if it should send this information. (see [Referrer policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)) +### Uploaded media and media proxy + +It is STRONGLY RECOMMENDED to serve both the locally-uploaded media and the media proxy from another domain than the domain that Pleroma runs on, if applicable. + +```elixir +config :pleroma, :media_proxy, + base_url: "https://some.other.domain" + +config :pleroma, Pleroma.Upload, + base_url: "https://some.other.domain" +``` + +See `installation/pleroma-mediaproxy.nginx` for examples on how to configure your media proxy. + ## systemd A systemd unit example is provided at `installation/pleroma.service`. diff --git a/installation/pleroma-mediaproxy.nginx b/installation/pleroma-mediaproxy.nginx new file mode 100644 index 000000000..6f2edf212 --- /dev/null +++ b/installation/pleroma-mediaproxy.nginx @@ -0,0 +1,97 @@ +# This file is for those who want to serve uploaded media and media proxy over +# another domain. This is STRONGLY RECOMMENDED. +# This is meant to be used ALONG WITH `pleroma.nginx`. + +# If this is a new instance, replace the `location ~ ^/(media|proxy)` section in +# `pleroma.nginx` with the following to completely disable access to media from the main domain: +# location ~ ^/(media|proxy) { +# return 404; +# } +# +# If you are configuring an existing instance to use another domain +# for media, you will want to keep redirecting all existing local media to the new domain +# so already-uploaded media will not break. +# Replace the `location ~ ^/(media|proxy)` section in `pleroma.nginx` with the following: +# +# location /media { +# return 301 https://some.other.domain$request_uri; +# } +# +# location /proxy { +# return 404; +# } + +server { + server_name some.other.domain; + + listen 80; + listen [::]:80; + + # Uncomment this if you need to use the 'webroot' method with certbot. Make sure + # that the directory exists and that it is accessible by the webserver. If you followed + # the guide, you already ran 'mkdir -p /var/lib/letsencrypt' to create the folder. + # You may need to load this file with the ssl server block commented out, run certbot + # to get the certificate, and then uncomment it. + # + # location ~ /\.well-known/acme-challenge { + # root /var/lib/letsencrypt/; + # } + location / { + return 301 https://$server_name$request_uri; + } +} + +server { + server_name some.other.domain; + + listen 443 ssl http2; + listen [::]:443 ssl http2; + ssl_session_timeout 1d; + ssl_session_cache shared:MozSSL:10m; # about 40000 sessions + ssl_session_tickets off; + + ssl_trusted_certificate /etc/letsencrypt/live/some.other.domain/chain.pem; + ssl_certificate /etc/letsencrypt/live/some.other.domain/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/some.other.domain/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + ssl_prefer_server_ciphers off; + # In case of an old server with an OpenSSL version of 1.0.2 or below, + # leave only prime256v1 or comment out the following line. + ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1; + ssl_stapling on; + ssl_stapling_verify on; + + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml; + + # the nginx default is 1m, not enough for large media uploads + client_max_body_size 16m; + ignore_invalid_headers off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + location / { return 404; } + + location ~ ^/(media|proxy) { + proxy_cache pleroma_media_cache; + slice 1m; + proxy_cache_key $host$uri$is_args$args$slice_range; + proxy_set_header Range $slice_range; + proxy_cache_valid 200 206 301 304 1h; + proxy_cache_lock on; + proxy_ignore_client_abort on; + proxy_buffering on; + chunked_transfer_encoding on; + proxy_pass http://phoenix; + } +} From 85902ad1ae5cfcc5d4c92cdb364a83d1edbbad3d Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 26 May 2023 17:27:35 -0400 Subject: [PATCH 0211/1008] Recommend users to serve media on another domain in guide --- docs/installation/otp_en.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md index f2812346b..ed52e57eb 100644 --- a/docs/installation/otp_en.md +++ b/docs/installation/otp_en.md @@ -198,6 +198,10 @@ $EDITOR path-to-nginx-config # Verify that the config is valid nginx -t ``` +#### (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. + #### Start nginx === "Alpine" From 408ea697aa685bb860deddfc63cd755a3b64c39a Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 26 May 2023 17:28:41 -0400 Subject: [PATCH 0212/1008] Add changelog --- changelog.d/media-altdomain.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/media-altdomain.skip diff --git a/changelog.d/media-altdomain.skip b/changelog.d/media-altdomain.skip new file mode 100644 index 000000000..e69de29bb From e92eb5f4823fbb6c01218ec72ad8d41fb12e20a1 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 27 May 2023 00:57:22 +0000 Subject: [PATCH 0213/1008] Add instructions to other distro's guides --- docs/installation/alpine_linux_en.md | 3 +++ docs/installation/arch_linux_en.md | 5 +++++ docs/installation/debian_based_en.md | 5 +++++ docs/installation/freebsd_en.md | 4 ++++ docs/installation/gentoo_en.md | 4 ++++ docs/installation/netbsd_en.md | 4 ++++ docs/installation/openbsd_en.md | 4 ++++ 7 files changed, 29 insertions(+) diff --git a/docs/installation/alpine_linux_en.md b/docs/installation/alpine_linux_en.md index c37ff0c63..7154bca48 100644 --- a/docs/installation/alpine_linux_en.md +++ b/docs/installation/alpine_linux_en.md @@ -183,6 +183,9 @@ server { ... } ``` +* (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. * Enable and start nginx: diff --git a/docs/installation/arch_linux_en.md b/docs/installation/arch_linux_en.md index 285743d56..f7d722ef9 100644 --- a/docs/installation/arch_linux_en.md +++ b/docs/installation/arch_linux_en.md @@ -173,6 +173,11 @@ sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/ple ``` * Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths) + +* (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. + * Enable and start nginx: ```shell diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md index 4e52b2155..fe85a05bf 100644 --- a/docs/installation/debian_based_en.md +++ b/docs/installation/debian_based_en.md @@ -136,6 +136,11 @@ sudo ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/ple ``` * Before starting nginx edit the configuration and change it to your needs (e.g. change servername, change cert paths) + +* (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. + * Enable and start nginx: ```shell diff --git a/docs/installation/freebsd_en.md b/docs/installation/freebsd_en.md index 9cbe0f203..50ed30d74 100644 --- a/docs/installation/freebsd_en.md +++ b/docs/installation/freebsd_en.md @@ -173,6 +173,10 @@ Edit the defaults of `/usr/local/etc/nginx/sites-available/pleroma.nginx`: * Change `ssl_certificate_key` to `/var/db/acme/certs/example.tld/example.tld.key`. * Change all references of `example.tld` to your instance's domain name. +#### (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. + ## Creating a startup script for Pleroma Pleroma will need to compile when it initially starts, which typically takes a longer diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md index 36882c8c8..693f83825 100644 --- a/docs/installation/gentoo_en.md +++ b/docs/installation/gentoo_en.md @@ -227,6 +227,10 @@ Replace all instances of `example.tld` with your instance's public URL. If for w Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stongly advised to comment that line out so that OpenSSL will use its full capabilities, and it is also possible you are running OpenSSL 1.0.2 necessitating that you do this. +* (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. + * Enable and start nginx: ```shell diff --git a/docs/installation/netbsd_en.md b/docs/installation/netbsd_en.md index 41b3b0072..2ade7df98 100644 --- a/docs/installation/netbsd_en.md +++ b/docs/installation/netbsd_en.md @@ -123,6 +123,10 @@ Edit the defaults: * Change `ssl_certificate_key` to `/etc/nginx/tls/key`. * Change `example.tld` to your instance's domain name. +### (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. + ## Configuring acme.sh We'll be using acme.sh in Stateless Mode for TLS certificate renewal. diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md index c80c8f678..9e7e040f5 100644 --- a/docs/installation/openbsd_en.md +++ b/docs/installation/openbsd_en.md @@ -195,6 +195,10 @@ rcctl enable relayd rcctl start relayd ``` +##### (Strongly recommended) serve media on another domain + +Refer to the [Hardening your instance](../configuration/hardening.md) document on how to serve media on another domain. We STRONGLY RECOMMEND you to do this to minimize attack vectors. + #### pf Enabling and configuring pf is highly recommended. In /etc/pf.conf, insert the following configuration: From b6b7de20100e980849404a9e7384617a15df1e43 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Thu, 18 May 2023 02:12:20 +0500 Subject: [PATCH 0214/1008] add url to Metadata.build_tags call If static_fe is enabled, going to https://pleroma/notice/some-id results in With this fix, it is Additionally, Pleroma.Web.Metadata.Providers.OpenGraph now generates meta tags for attachments in the post. --- lib/pleroma/web/static_fe/static_fe_controller.ex | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index 97c41c6f9..8019a218a 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -25,7 +25,15 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do true <- Visibility.is_public?(activity.object), {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)}, %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do - meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user}) + url = Helpers.url(conn) <> conn.request_path + + meta = + Metadata.build_tags(%{ + activity_id: notice_id, + object: activity.object, + user: user, + url: url + }) timeline = activity.object.data["context"] From 52368e670222c2add5f5677d864dd64c9376c553 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Thu, 18 May 2023 02:17:29 +0500 Subject: [PATCH 0215/1008] fix meta tag for twitter cards and image attachments The name of the tag should be twitter:image, not twitter:player. Also, add twitter:image:alt meta tags. --- lib/pleroma/web/metadata/providers/twitter_card.ex | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex index 2dac22ee2..fbd4fd5a7 100644 --- a/lib/pleroma/web/metadata/providers/twitter_card.ex +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex @@ -76,9 +76,11 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do {:meta, [name: "twitter:card", content: "summary_large_image"], []}, {:meta, [ - name: "twitter:player", + name: "twitter:image", content: MediaProxy.url(url["href"]) - ], []} + ], []}, + {:meta, [property: "twitter:image:alt", content: truncate(attachment["name"])], + []} | acc ] |> maybe_add_dimensions(url) @@ -130,4 +132,10 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do metadata end end + + defp truncate(text) do + # truncate to 420 characters + # see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + Pleroma.Formatter.truncate(text, 420) + end end From a1af122499d3558d9fa6d5606c394e01f36e95e4 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Thu, 18 May 2023 02:28:50 +0500 Subject: [PATCH 0216/1008] changelog entry --- changelog.d/3885.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3885.fix diff --git a/changelog.d/3885.fix b/changelog.d/3885.fix new file mode 100644 index 000000000..c5fbb0ed4 --- /dev/null +++ b/changelog.d/3885.fix @@ -0,0 +1 @@ +Fix opengraph and twitter card meta tags From 8b390d27dce0c1827ed4c8c959612b6126624c9a Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Thu, 18 May 2023 03:20:44 +0500 Subject: [PATCH 0217/1008] twitter card: handle case where image has no alt text --- lib/pleroma/web/metadata/providers/twitter_card.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex index fbd4fd5a7..426022c65 100644 --- a/lib/pleroma/web/metadata/providers/twitter_card.ex +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex @@ -79,8 +79,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do name: "twitter:image", content: MediaProxy.url(url["href"]) ], []}, - {:meta, [property: "twitter:image:alt", content: truncate(attachment["name"])], - []} + {:meta, [name: "twitter:image:alt", content: truncate(attachment["name"])], []} | acc ] |> maybe_add_dimensions(url) @@ -133,6 +132,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do end end + defp truncate(nil), do: "" + defp truncate(text) do # truncate to 420 characters # see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup From 4c91c0d1baa5bdfdcbfc6ff54859be7e9db91a37 Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Thu, 18 May 2023 03:21:14 +0500 Subject: [PATCH 0218/1008] oops, forgot the test cases --- test/pleroma/web/metadata/providers/twitter_card_test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs index be4cfbe7b..f8d01c5c8 100644 --- a/test/pleroma/web/metadata/providers/twitter_card_test.exs +++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs @@ -182,7 +182,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do {:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}, {:meta, [name: "twitter:description", content: "pleroma in a nutshell"], []}, {:meta, [name: "twitter:card", content: "summary_large_image"], []}, - {:meta, [name: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []}, + {:meta, [name: "twitter:image", content: "https://pleroma.gov/tenshi.png"], []}, + {:meta, [name: "twitter:image:alt", content: ""], []}, {:meta, [name: "twitter:player:width", content: "1280"], []}, {:meta, [name: "twitter:player:height", content: "1024"], []}, {:meta, [name: "twitter:card", content: "player"], []}, From 506a1c98e716754455387be9ace3ad7aec9c47a3 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 13:55:48 -0400 Subject: [PATCH 0219/1008] ConnCase: Make sure the host we use in tests is the actual Endpoint host --- test/support/conn_case.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index f010fec33..d6729ab87 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -120,6 +120,10 @@ defmodule Pleroma.Web.ConnCase do Mox.verify_on_exit!() - {:ok, conn: Phoenix.ConnTest.build_conn()} + {:ok, + conn: + Phoenix.ConnTest.build_conn() + |> Map.put(:host, Pleroma.Web.Endpoint.host()) + |> Plug.Test.init_test_session(%{})} end end From 843fcca5b4d022e4c088d4a60839b4a286500148 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 13:59:51 -0400 Subject: [PATCH 0220/1008] Validate Host header matches expected value before allowing access to MediaProxy --- .../web/media_proxy/media_proxy_controller.ex | 12 ++++++++++++ .../media_proxy/media_proxy_controller_test.exs | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index bda5b36ed..767496b68 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Web.MediaProxy alias Plug.Conn + plug(:validate_host) plug(:sandbox) def remote(conn, %{"sig" => sig64, "url" => url64}) do @@ -205,6 +206,17 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do Config.get([:media_proxy, :proxy_opts], []) end + defp validate_host(conn, _params) do + proxy_host = MediaProxy.base_url() |> URI.parse() |> Map.get(:host) + + if match?(^proxy_host, conn.host) do + conn + else + send_resp(conn, 400, Conn.Status.reason_phrase(400)) + |> halt() + end + end + defp sandbox(conn, _params) do conn |> merge_resp_headers([{"content-security-policy", "sandbox;"}]) diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index 9ce092fd8..3971330d4 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -54,6 +54,23 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do } = get(conn, "/proxy/hhgfh/eeee/fff") end + test "it returns a 400 for invalid host", %{conn: conn} do + clear_config([:media_proxy, :base_url], "http://mp.localhost/") + + url = + MediaProxy.encode_url("https://pleroma.social/logo.jpeg") + |> URI.parse() + |> Map.put(:host, "wronghost") + |> URI.to_string() + + with_mock Pleroma.ReverseProxy, + call: fn _conn, _url, _opts -> %Conn{status: :success} end do + %{status: status} = get(conn, url) + + assert status == 400 + end + end + test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do invalid_url = String.replace(url, "test.png", "test-file.png") response = get(conn, invalid_url) From a60dd0d92dae2471025827bc57d3cf3194003110 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 14:16:03 -0400 Subject: [PATCH 0221/1008] Validate Host header matches expected value before allowing access to Uploads --- lib/pleroma/web/plugs/uploaded_media.ex | 11 ++++++++++- .../pleroma/web/plugs/uploaded_media_plug_test.exs | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 8b3bc9acb..49db9808f 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -46,12 +46,21 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do config = Pleroma.Config.get(Pleroma.Upload) - with uploader <- Keyword.fetch!(config, :uploader), + media_host = Pleroma.Upload.base_url() |> URI.parse() |> Map.get(:host) + + with {:valid_host, true} <- {:valid_host, match?(^media_host, conn.host)}, + uploader <- Keyword.fetch!(config, :uploader), proxy_remote = Keyword.get(config, :proxy_remote, false), {:ok, get_method} <- uploader.get_file(file), false <- media_is_banned(conn, get_method) do get_media(conn, get_method, proxy_remote, opts) else + {:valid_host, false} -> + conn + + send_resp(conn, 400, Plug.Conn.Status.reason_phrase(400)) + |> halt() + _ -> conn |> send_resp(:internal_server_error, dgettext("errors", "Failed")) diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs index 8323ff6ab..262c8c288 100644 --- a/test/pleroma/web/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -40,4 +40,18 @@ defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do &(&1 == {"content-disposition", ~s[inline; filename="\\"cofe\\".gif"]}) ) end + + test "denies access to media if wrong Host", %{ + attachment_url: attachment_url + } do + conn = get(build_conn(), attachment_url) + + assert conn.status == 200 + + clear_config([Pleroma.Upload, :base_url], "http://media.localhost/") + + conn = get(build_conn(), attachment_url) + + assert conn.status == 400 + end end From 84974efe4c6e26f20bc14fece6cc74efe8777547 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 14:17:27 -0400 Subject: [PATCH 0222/1008] Host header validation is now required for MediaProxy and Uploads --- changelog.d/3896.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3896.add diff --git a/changelog.d/3896.add b/changelog.d/3896.add new file mode 100644 index 000000000..3124e07dd --- /dev/null +++ b/changelog.d/3896.add @@ -0,0 +1 @@ +Validate Host header for MediaProxy and Uploads From 43bb2f39db0bd7028512cba402f6b5aa1610c1db Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 15:05:37 -0400 Subject: [PATCH 0223/1008] Remove unwanted parameter --- test/support/conn_case.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index d6729ab87..c1cb0295b 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -123,7 +123,6 @@ defmodule Pleroma.Web.ConnCase do {:ok, conn: Phoenix.ConnTest.build_conn() - |> Map.put(:host, Pleroma.Web.Endpoint.host()) - |> Plug.Test.init_test_session(%{})} + |> Map.put(:host, Pleroma.Web.Endpoint.host())} end end From da7394f33b4402325b220dcd2957945464517f8b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 15:09:31 -0400 Subject: [PATCH 0224/1008] Fix unused assignment --- lib/pleroma/web/plugs/uploaded_media.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 49db9808f..f97c64cee 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -56,8 +56,6 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do get_media(conn, get_method, proxy_remote, opts) else {:valid_host, false} -> - conn - send_resp(conn, 400, Plug.Conn.Status.reason_phrase(400)) |> halt() From 9caa0b0be1feffb20b3eaccb48298de572d32393 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 15:46:49 -0400 Subject: [PATCH 0225/1008] Add OnlyMedia Upload Filter to simplify restricting uploads to audio, image, and video types --- changelog.d/3897.add | 1 + docs/configuration/cheatsheet.md | 6 ++++ lib/pleroma/upload/filter.ex | 6 ++-- lib/pleroma/upload/filter/only_media.ex | 20 ++++++++++++ .../pleroma/upload/filter/only_media_test.exs | 32 +++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 changelog.d/3897.add create mode 100644 lib/pleroma/upload/filter/only_media.ex create mode 100644 test/pleroma/upload/filter/only_media_test.exs diff --git a/changelog.d/3897.add b/changelog.d/3897.add new file mode 100644 index 000000000..5c4402f45 --- /dev/null +++ b/changelog.d/3897.add @@ -0,0 +1 @@ +OnlyMedia Upload Filter diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 70abe3e0c..026753f38 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -671,6 +671,12 @@ This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exi No specific configuration. +#### Pleroma.Upload.Filter.OnlyMedia + +This filter rejects uploads that are not identified with Content-Type matching audio/*, image/*, or video/* + +No specific configuration. + #### Pleroma.Upload.Filter.Mogrify * `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`. diff --git a/lib/pleroma/upload/filter.ex b/lib/pleroma/upload/filter.ex index 717f06621..809bc6e70 100644 --- a/lib/pleroma/upload/filter.ex +++ b/lib/pleroma/upload/filter.ex @@ -38,9 +38,9 @@ defmodule Pleroma.Upload.Filter do {:ok, :noop} -> filter(rest, upload) - error -> - Logger.error("#{__MODULE__}: Filter #{filter} failed: #{inspect(error)}") - error + {:error, e} -> + Logger.error("#{__MODULE__}: Filter #{filter} failed: #{inspect(e)}") + {:error, e} end end end diff --git a/lib/pleroma/upload/filter/only_media.ex b/lib/pleroma/upload/filter/only_media.ex new file mode 100644 index 000000000..a9caeba67 --- /dev/null +++ b/lib/pleroma/upload/filter/only_media.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Upload.Filter.OnlyMedia do + @behaviour Pleroma.Upload.Filter + alias Pleroma.Upload + + def filter(%Upload{content_type: content_type}) do + [type, _subtype] = String.split(content_type, "/") + + if type in ["image", "video", "audio"] do + {:ok, :noop} + else + {:error, "Disallowed content-type: #{content_type}"} + end + end + + def filter(_), do: {:ok, :noop} +end diff --git a/test/pleroma/upload/filter/only_media_test.exs b/test/pleroma/upload/filter/only_media_test.exs new file mode 100644 index 000000000..75be070a1 --- /dev/null +++ b/test/pleroma/upload/filter/only_media_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Upload.Filter.OnlyMediaTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Upload + alias Pleroma.Upload.Filter.OnlyMedia + + test "Allows media Content-Type" do + ["audio/mpeg", "image/jpeg", "video/mp4"] + |> Enum.each(fn type -> + upload = %Upload{ + content_type: type + } + + assert {:ok, :noop} = OnlyMedia.filter(upload) + end) + end + + test "Disallows non-media Content-Type" do + ["application/javascript", "application/pdf", "text/html"] + |> Enum.each(fn type -> + upload = %Upload{ + content_type: type + } + + assert {:error, _} = OnlyMedia.filter(upload) + end) + end +end From 50a20f3bbd1203679c9295eb6002225dd630baf8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 May 2023 15:53:16 -0400 Subject: [PATCH 0226/1008] Esacpe the asterisks in Markdown --- 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 026753f38..1e49a79d0 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -673,7 +673,7 @@ No specific configuration. #### Pleroma.Upload.Filter.OnlyMedia -This filter rejects uploads that are not identified with Content-Type matching audio/*, image/*, or video/* +This filter rejects uploads that are not identified with Content-Type matching audio/\*, image/\*, or video/\* No specific configuration. From b3c3bd99c390a4e5081d411011688e38285547b0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 30 May 2023 16:56:09 -0400 Subject: [PATCH 0227/1008] Switch from serving a 400 to a 302 --- changelog.d/3896.add | 2 +- .../web/media_proxy/media_proxy_controller.ex | 17 ++++++++++-- lib/pleroma/web/plugs/uploaded_media.ex | 17 ++++++++++-- .../media_proxy_controller_test.exs | 27 +++++++++++++++---- .../web/plugs/uploaded_media_plug_test.exs | 21 +++++++++++++-- 5 files changed, 72 insertions(+), 12 deletions(-) diff --git a/changelog.d/3896.add b/changelog.d/3896.add index 3124e07dd..36d8286ff 100644 --- a/changelog.d/3896.add +++ b/changelog.d/3896.add @@ -1 +1 @@ -Validate Host header for MediaProxy and Uploads +Validate Host header for MediaProxy and Uploads and return a 302 if the base_url has changed diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 767496b68..20f3a3438 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -207,12 +207,25 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do end defp validate_host(conn, _params) do - proxy_host = MediaProxy.base_url() |> URI.parse() |> Map.get(:host) + %{scheme: proxy_scheme, host: proxy_host, port: proxy_port} = + MediaProxy.base_url() |> URI.parse() if match?(^proxy_host, conn.host) do conn else - send_resp(conn, 400, Conn.Status.reason_phrase(400)) + redirect_url = + %URI{ + scheme: proxy_scheme, + host: proxy_host, + port: proxy_port, + path: conn.request_path, + query: conn.query_string + } + |> URI.to_string() + |> String.trim_trailing("?") + + conn + |> Phoenix.Controller.redirect(external: redirect_url) |> halt() end end diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index f97c64cee..9dd5eb239 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -46,7 +46,8 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do config = Pleroma.Config.get(Pleroma.Upload) - media_host = Pleroma.Upload.base_url() |> URI.parse() |> Map.get(:host) + %{scheme: media_scheme, host: media_host, port: media_port} = + Pleroma.Upload.base_url() |> URI.parse() with {:valid_host, true} <- {:valid_host, match?(^media_host, conn.host)}, uploader <- Keyword.fetch!(config, :uploader), @@ -56,7 +57,19 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do get_media(conn, get_method, proxy_remote, opts) else {:valid_host, false} -> - send_resp(conn, 400, Plug.Conn.Status.reason_phrase(400)) + redirect_url = + %URI{ + scheme: media_scheme, + host: media_host, + port: media_port, + path: conn.request_path, + query: conn.query_string + } + |> URI.to_string() + |> String.trim_trailing("?") + + conn + |> Phoenix.Controller.redirect(external: redirect_url) |> halt() _ -> diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index 3971330d4..019e389b7 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -54,20 +54,37 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do } = get(conn, "/proxy/hhgfh/eeee/fff") end - test "it returns a 400 for invalid host", %{conn: conn} do - clear_config([:media_proxy, :base_url], "http://mp.localhost/") + test "it returns a 302 for invalid host", %{conn: conn} do + new_proxy_base = "http://mp.localhost/" - url = + %{scheme: new_proxy_scheme, host: new_proxy_host, port: new_proxy_port} = + URI.parse(new_proxy_base) + + clear_config([:media_proxy, :base_url], new_proxy_base) + + proxy_url = MediaProxy.encode_url("https://pleroma.social/logo.jpeg") |> URI.parse() |> Map.put(:host, "wronghost") |> URI.to_string() + expected_url = + URI.parse(proxy_url) + |> Map.put(:host, new_proxy_host) + |> Map.put(:port, new_proxy_port) + |> Map.put(:scheme, new_proxy_scheme) + |> URI.to_string() + with_mock Pleroma.ReverseProxy, call: fn _conn, _url, _opts -> %Conn{status: :success} end do - %{status: status} = get(conn, url) + %{resp_headers: resp_headers, status: status} = get(conn, proxy_url) - assert status == 400 + assert status == 302 + + assert Enum.any?( + resp_headers, + &(&1 == {"location", expected_url}) + ) end end diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs index 262c8c288..fcc523cf4 100644 --- a/test/pleroma/web/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -48,10 +48,27 @@ defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do assert conn.status == 200 - clear_config([Pleroma.Upload, :base_url], "http://media.localhost/") + new_media_base = "http://media.localhost:8080" + + %{scheme: new_media_scheme, host: new_media_host, port: new_media_port} = + URI.parse(new_media_base) + + clear_config([Pleroma.Upload, :base_url], new_media_base) conn = get(build_conn(), attachment_url) - assert conn.status == 400 + expected_url = + URI.parse(attachment_url) + |> Map.put(:host, new_media_host) + |> Map.put(:port, new_media_port) + |> Map.put(:scheme, new_media_scheme) + |> URI.to_string() + + assert conn.status == 302 + + assert Enum.any?( + conn.resp_headers, + &(&1 == {"location", expected_url}) + ) end end From 8336519f300e2e12087126564a155c292915daed Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 31 May 2023 08:26:21 +0200 Subject: [PATCH 0228/1008] installation/debian_based_en: Elixir 1.11 means Debian 12+ and Ubuntu 22.04+ --- docs/installation/debian_based_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/debian_based_en.md b/docs/installation/debian_based_en.md index 4e52b2155..575b8a7ab 100644 --- a/docs/installation/debian_based_en.md +++ b/docs/installation/debian_based_en.md @@ -4,7 +4,7 @@ ## Installation -This guide will assume you are on Debian 11 (“bullseye”) or later. This guide should also work with Ubuntu 18.04 (“Bionic Beaver”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -s $SHELL -c 'command'` instead. +This guide will assume you are on Debian 12 (“bookworm”) or later. This guide should also work with Ubuntu 22.04 (“jammy”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su -s $SHELL -c 'command'` instead. {! backend/installation/generic_dependencies.include !} From 737e45c1022172b316671110468a03a22a23e809 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 31 May 2023 08:28:28 +0200 Subject: [PATCH 0229/1008] installation/debian_based_jp: Elixir 1.11 means Debian 12+ and Ubuntu 22.04+ I checked for what each part of the debian&ubuntu sentences meant with the help of Jisho.org, should be safe but it did make me notice that this guide hasn't been updated in years --- changelog.d/distro-docs-elixir-1.11.skip | 0 docs/installation/debian_based_jp.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/distro-docs-elixir-1.11.skip diff --git a/changelog.d/distro-docs-elixir-1.11.skip b/changelog.d/distro-docs-elixir-1.11.skip new file mode 100644 index 000000000..e69de29bb diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md index 3736e857f..695e7bb7d 100644 --- a/docs/installation/debian_based_jp.md +++ b/docs/installation/debian_based_jp.md @@ -5,7 +5,7 @@ ## インストール -このガイドはDebian Stretchを利用することを想定しています。Ubuntu 16.04や18.04でもおそらく動作します。また、ユーザはrootもしくはsudoにより管理者権限を持っていることを前提とします。もし、以下の操作をrootユーザで行う場合は、 `sudo` を無視してください。ただし、`sudo -Hu pleroma` のようにユーザを指定している場合には `su -s $SHELL -c 'command'` を代わりに使ってください。 +このガイドはDebian Bookwormを利用することを想定しています。Ubuntu 22.04でもおそらく動作します。また、ユーザはrootもしくはsudoにより管理者権限を持っていることを前提とします。もし、以下の操作をrootユーザで行う場合は、 `sudo` を無視してください。ただし、`sudo -Hu pleroma` のようにユーザを指定している場合には `su -s $SHELL -c 'command'` を代わりに使ってください。 ### 必要なソフトウェア From 46c799f5284afe26756a6dd1b247af32de3be929 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 09:50:47 -0400 Subject: [PATCH 0230/1008] Use Phoenix.ConnTest.redirected_to/2 --- changelog.d/3899.skip | 0 .../web/media_proxy/media_proxy_controller_test.exs | 9 ++------- test/pleroma/web/plugs/uploaded_media_plug_test.exs | 7 +------ 3 files changed, 3 insertions(+), 13 deletions(-) create mode 100644 changelog.d/3899.skip diff --git a/changelog.d/3899.skip b/changelog.d/3899.skip new file mode 100644 index 000000000..e69de29bb diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index 019e389b7..deb407709 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -77,14 +77,9 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do with_mock Pleroma.ReverseProxy, call: fn _conn, _url, _opts -> %Conn{status: :success} end do - %{resp_headers: resp_headers, status: status} = get(conn, proxy_url) + conn = get(conn, proxy_url) - assert status == 302 - - assert Enum.any?( - resp_headers, - &(&1 == {"location", expected_url}) - ) + assert redirected_to(conn, 302) == expected_url end end diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs index fcc523cf4..dbf8ca5ec 100644 --- a/test/pleroma/web/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -64,11 +64,6 @@ defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do |> Map.put(:scheme, new_media_scheme) |> URI.to_string() - assert conn.status == 302 - - assert Enum.any?( - conn.resp_headers, - &(&1 == {"location", expected_url}) - ) + assert redirected_to(conn, 302) == expected_url end end From bed44f9f628c3e184ba367765c87c47656060f28 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 13:10:18 -0400 Subject: [PATCH 0231/1008] Update to Phoenix 1.7 Chase the other deps to match versions you get from a current "mix phx.new" project creation --- mix.exs | 15 ++++++++------- mix.lock | 24 +++++++++++++----------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/mix.exs b/mix.exs index bb16f0a9c..3fcbfffaf 100644 --- a/mix.exs +++ b/mix.exs @@ -113,18 +113,19 @@ defmodule Pleroma.Mixfile do # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.6.0"}, - {:phoenix_ecto, "~> 4.4.0"}, + {:phoenix, "~> 1.7.3"}, + {:phoenix_ecto, "~> 4.4"}, + {:ecto_sql, "~> 3.10"}, {:ecto_enum, "~> 1.4"}, {:postgrex, ">= 0.0.0"}, - {:phoenix_html, "~> 3.1"}, + {:phoenix_html, "~> 3.3"}, {:phoenix_live_reload, "~> 1.3.3", only: :dev}, - {:phoenix_live_view, "~> 0.17.1"}, - {:phoenix_live_dashboard, "~> 0.6.2"}, - {:telemetry_metrics, "~> 0.6.1"}, + {:phoenix_live_view, "~> 0.19.0"}, + {:phoenix_live_dashboard, "~> 0.8.0"}, + {:telemetry_metrics, "~> 0.6"}, {:telemetry_poller, "~> 1.0"}, {:tzdata, "~> 1.0.3"}, - {:plug_cowboy, "~> 2.3"}, + {:plug_cowboy, "~> 2.5"}, # oban 2.14 requires Elixir 1.12+ {:oban, "~> 2.13.4"}, {:gettext, diff --git a/mix.lock b/mix.lock index 43012a58e..fd8e02f95 100644 --- a/mix.lock +++ b/mix.lock @@ -16,22 +16,22 @@ "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"}, "covertool": {:hex, :covertool, "2.0.4", "54acff6cddd88d28dea663cd2e1fe20dd32fcf5f5d3aff7d59031ce44ce39efa", [:rebar3], [], "hexpm", "5c9568ba4308fda2082172737c80c31d991ea83961eb10791f06106a870d0cdc"}, - "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, - "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, - "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.22", "ea3e45c6359446dc308be0a64ce82a03260d973de7d0625a762e6d352ff57958", [:mix], [{:earmark_parser, "~> 1.4.23", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "1caf5145665a42fd76d5317286b0c171861fb1c04f86ab103dde76868814fdfb"}, "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"}, - "ecto": {:hex, :ecto, "3.9.5", "9f0aa7ae44a1577b651c98791c6988cd1b69b21bc724e3fd67090b97f7604263", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d4f3115d8cbacdc0bfa4b742865459fb1371d0715515842a1fb17fe31920b74c"}, + "ecto": {:hex, :ecto, "3.10.1", "c6757101880e90acc6125b095853176a02da8f1afe056f91f1f90b80c9389822", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2ac4255f1601bdf7ac74c0ed971102c6829dc158719b94bd30041bbad77f87a"}, "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_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"}, - "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.11", "6e20144c1446dcccfcdb4c142c9d8b7992a90a569b1d5958cbea5458550b25f0", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "def61f1f92d4f40d51c80bbae2157212d6c0a459eb604be446e47369cbd40b23"}, + "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, @@ -84,13 +84,13 @@ "open_api_spex": {:hex, :open_api_spex, "3.16.1", "8137c338129d63060b4b04947c6c57429f86267045c479c703a38a6d3f98dee1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "ef6fd778ac121af866b48b75ad4ad256b6ff33949113ea4aa1629af8bfdfdbfb"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, - "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, + "phoenix": {:hex, :phoenix, "1.7.3", "4d8eca2c020c9ed81a28e7a8c60e0a4f6f9f7f6e12eb91dfd01301eac07424c1", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "6b1bc308758f95ecf3e0d795389440a2ca88a903e0fda1f921c780918c16d640"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, - "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.0", "0b3158b5b198aa444473c91d23d79f52fb077e807ffad80dacf88ce078fa8df2", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "87785a54474fed91a67a1227a741097eb1a42c2e49d3c0d098b588af65cd410d"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.0", "de5643d03e3cdf5ff19cd45b5d14543a3b1ad8551d529f6b24246e88a6c6f1b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a650b6f814c4f386314b98f1aebf92f8652649166612f84ef2e60a20894addfa"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.2", "a4950b63ace57720b0fc1c6da083c53346b36f99021de89595cc4f026288ff51", [:mix], [], "hexpm", "45741676a94c71f9afdfed9d22d49b6856c026ff504db04e3dc03a1d86f8201c"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, @@ -100,7 +100,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.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, + "postgrex": {:hex, :postgrex, "0.17.1", "01c29fd1205940ee55f7addb8f1dc25618ca63a8817e56fac4f6846fc2cddcbe", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "14b057b488e73be2beee508fb1955d8db90d6485c6466428fe9ccf1d6692a555"}, "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, "prom_ex": {:hex, :prom_ex, "1.7.1", "39331ee3fe6f9a8587d8208bf9274a253bb80281700e127dd18786cda5e08c37", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.10.2", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "4c978872b88a929833925a0f4d0561824804c671fdd04581e765509ed0a6ed08"}, "prometheus": {:hex, :prometheus, "4.10.0", "792adbf0130ff61b5fa8826f013772af24b6e57b984445c8d602c8a0355704a1", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "2a99bb6dce85e238c7236fde6b0064f9834dc420ddbd962aac4ea2a3c3d59384"}, @@ -132,5 +132,7 @@ "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, + "websock": {:hex, :websock, "0.5.1", "c496036ce95bc26d08ba086b2a827b212c67e7cabaa1c06473cd26b40ed8cf10", [:mix], [], "hexpm", "b9f785108b81cd457b06e5f5dabe5f65453d86a99118b2c0a515e1e296dc2d2c"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.1", "292e6c56724e3457e808e525af0e9bcfa088cc7b9c798218e78658c7f9b85066", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8e2e1544bfde5f9d0442f9cec2f5235398b224f75c9e06b60557debf64248ec1"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, } From 6e1ea2eafc24cb31e796e93313dcf9c6b6ba9845 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 13:36:07 -0400 Subject: [PATCH 0232/1008] Remove unnecessary compilers as of Phoenix 1.7 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 3fcbfffaf..9eafa2474 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule Pleroma.Mixfile do version: version("2.5.52"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), - compilers: [:phoenix, :gettext] ++ Mix.compilers(), + compilers: Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], xref: [exclude: [:eldap]], start_permanent: Mix.env() == :prod, From e3110cb34e350bc9353d082328778daae47ba9e8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 13:36:21 -0400 Subject: [PATCH 0233/1008] Fix deprecated calls to get_flash/2 --- lib/pleroma/web.ex | 2 +- lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex | 8 ++++---- lib/pleroma/web/templates/o_auth/mfa/totp.html.eex | 8 ++++---- lib/pleroma/web/templates/o_auth/o_auth/register.html.eex | 8 ++++---- lib/pleroma/web/templates/o_auth/o_auth/show.html.eex | 8 ++++---- test/pleroma/web/o_auth/o_auth_controller_test.exs | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index aee41b0fe..7a8b176cd 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -136,7 +136,7 @@ defmodule Pleroma.Web do namespace: Pleroma.Web # Import convenience functions from controllers - import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1] + import Phoenix.Controller, only: [get_csrf_token: 0, view_module: 1] import Pleroma.Web.ErrorHelpers import Pleroma.Web.Gettext diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex index e45d13bdf..e3639aae7 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> - +<%= if Phoenix.Flash.get(@flash, :info) do %> + <% end %> -<%= if get_flash(@conn, :error) do %> - +<%= if Phoenix.Flash.get(@flash, :error) do %> + <% end %>

<%= Gettext.dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %>

diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex index 50e6c04b6..f995b8805 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> - +<%= if Phoenix.Flash.get(@flash, :info) do %> + <% end %> -<%= if get_flash(@conn, :error) do %> - +<%= if Phoenix.Flash.get(@flash, :error) do %> + <% end %>

<%= Gettext.dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %>

diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex index 1f661efb2..e7f65266f 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> - +<%= if Phoenix.Flash.get(@flash, :info) do %> + <% end %> -<%= if get_flash(@conn, :error) do %> - +<%= if Phoenix.Flash.get(@flash, :error) do %> + <% end %>

<%= Gettext.dpgettext("static_pages", "oauth register page title", "Registration Details") %>

diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index b3654f3eb..5b38f7142 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -1,8 +1,8 @@ -<%= if get_flash(@conn, :info) do %> - +<%= if Phoenix.Flash.get(@flash, :info) do %> + <% end %> -<%= if get_flash(@conn, :error) do %> - +<%= if Phoenix.Flash.get(@flash, :error) do %> + <% end %> <%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs index f41d6a322..83a08d9fc 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -186,7 +186,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do assert html_response(conn, 302) assert redirected_to(conn) == app.redirect_uris - assert get_flash(conn, :error) == "Failed to authenticate: (error description)." + assert conn.assigns.flash["error"] == "Failed to authenticate: (error description)." end test "GET /oauth/registration_details renders registration details form", %{ @@ -307,7 +307,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do |> post("/oauth/register", bad_params) assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/ - assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken." + assert conn.assigns.flash["error"] == "Error: #{bad_param} has already been taken." end end @@ -398,7 +398,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do |> post("/oauth/register", params) assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/ - assert get_flash(conn, :error) == "Invalid Username/Password" + assert conn.assigns.flash["error"] == "Invalid Username/Password" end end From f622f82c0e1ca66f1dc3493d900f60a24ab96865 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 13:38:46 -0400 Subject: [PATCH 0234/1008] No user facing changes --- changelog.d/3900.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/3900.skip diff --git a/changelog.d/3900.skip b/changelog.d/3900.skip new file mode 100644 index 000000000..e69de29bb From a7e7db4a29a09dc2193455de696d5cda561db5d4 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 13:47:15 -0400 Subject: [PATCH 0235/1008] Phoenix.Endpoint.Cowboy2Handler -> Plug.Cowboy.Handler --- config/config.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 178b6be99..6c9eb9bfc 100644 --- a/config/config.exs +++ b/config/config.exs @@ -133,7 +133,7 @@ config :pleroma, Pleroma.Web.Endpoint, {"/websocket", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} + {:_, Plug.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} ]} ] ], From a2bbd7c9dadad42c995e7627a559673f04c2618b Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 31 May 2023 12:22:13 -0600 Subject: [PATCH 0236/1008] Fix base media and proxy URL in instructions to serve media on another domain --- docs/configuration/hardening.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration/hardening.md b/docs/configuration/hardening.md index 4f40873e6..493ba608c 100644 --- a/docs/configuration/hardening.md +++ b/docs/configuration/hardening.md @@ -68,10 +68,10 @@ It is STRONGLY RECOMMENDED to serve both the locally-uploaded media and the medi ```elixir config :pleroma, :media_proxy, - base_url: "https://some.other.domain" + base_url: "https://some.other.domain/proxy" config :pleroma, Pleroma.Upload, - base_url: "https://some.other.domain" + base_url: "https://some.other.domain/media" ``` See `installation/pleroma-mediaproxy.nginx` for examples on how to configure your media proxy. From 4dc2d4bf7b39ca427fdb9be90a9789b7c4d9824e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 15:30:31 -0400 Subject: [PATCH 0237/1008] Remove locked version of plug Required for Phoenix 1.7 websocket changes --- mix.exs | 3 --- mix.lock | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index 9eafa2474..e346834e5 100644 --- a/mix.exs +++ b/mix.exs @@ -197,9 +197,6 @@ defmodule Pleroma.Mixfile do {:open_api_spex, "~> 3.16"}, {:ecto_psql_extras, "~> 0.6"}, - # indirect dependency version override - {:plug, "~> 1.10.4", override: true}, - ## dev & test {:ex_doc, "~> 0.22", only: :dev, runtime: false}, {:ex_machina, "~> 2.4", only: :test}, diff --git a/mix.lock b/mix.lock index fd8e02f95..65863fc40 100644 --- a/mix.lock +++ b/mix.lock @@ -94,7 +94,7 @@ "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, - "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, + "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "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"}, From ffee478ed0298f2cf29d4d51ed28105119552496 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 15:30:58 -0400 Subject: [PATCH 0238/1008] Move websocket config for Shoutbox to the Endpoint This is the modern way of configuring it --- config/config.exs | 14 -------------- lib/pleroma/web/endpoint.ex | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/config/config.exs b/config/config.exs index 6c9eb9bfc..fb1903db9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -110,17 +110,6 @@ config :pleroma, :uri_schemes, "xmpp" ] -websocket_config = [ - path: "/websocket", - serializer: [ - {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"}, - {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"} - ], - timeout: 60_000, - transport_log: false, - compress: false -] - # Configures the endpoint config :pleroma, Pleroma.Web.Endpoint, url: [host: "localhost"], @@ -130,9 +119,6 @@ config :pleroma, Pleroma.Web.Endpoint, {:_, [ {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {"/websocket", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}}, {:_, Plug.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} ]} ] diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index d8d40cceb..9e0340cd2 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -9,7 +9,20 @@ defmodule Pleroma.Web.Endpoint do alias Pleroma.Config - socket("/socket", Pleroma.Web.UserSocket) + socket("/socket", Pleroma.Web.UserSocket, + websocket: [ + path: "/websocket", + serializer: [ + {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"}, + {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"} + ], + timeout: 60_000, + transport_log: false, + compress: false + ], + longpoll: false + ) + socket("/live", Phoenix.LiveView.Socket) plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint]) From 62322f71e2f2e607ee66a91a99e35eecbf89914d Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 16:22:40 -0400 Subject: [PATCH 0239/1008] Clean up Plug.Parsers.MULTIPART deprecation warnings There is no need to the length setting to :multipart. The length setting is global for all of the parsers. --- lib/pleroma/web/endpoint.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 9e0340cd2..201a92653 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -116,7 +116,7 @@ defmodule Pleroma.Web.Endpoint do plug(Plug.Parsers, parsers: [ :urlencoded, - {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}}, + :multipart, :json ], pass: ["*/*"], From ba988a9abc6cb92d47b14f9ddf8ac78946215972 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 16:30:31 -0400 Subject: [PATCH 0240/1008] Fix test warnings warning: the URI path used in plug tests must start with "/" --- .../controllers/status_controller_test.exs | 22 +++++++++---------- .../controllers/timeline_controller_test.exs | 12 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 1e8979127..563034355 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -37,7 +37,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do response = conn |> put_req_header("content-type", "application/json") - |> post("api/v1/statuses", %{ + |> post("/api/v1/statuses", %{ "content_type" => "text/plain", "source" => "Pleroma FE", "status" => "Hello world", @@ -50,7 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do response = conn - |> get("api/v1/statuses/#{response["id"]}", %{}) + |> get("/api/v1/statuses/#{response["id"]}", %{}) |> json_response_and_validate_schema(200) assert response["reblogs_count"] == 0 @@ -109,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do conn_four = conn |> put_req_header("content-type", "application/json") - |> post("api/v1/statuses", %{ + |> post("/api/v1/statuses", %{ "status" => "oolong", "expires_in" => expires_in }) @@ -134,7 +134,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert %{"error" => "Expiry date is too soon"} = conn |> put_req_header("content-type", "application/json") - |> post("api/v1/statuses", %{ + |> post("/api/v1/statuses", %{ "status" => "oolong", "expires_in" => expires_in }) @@ -146,7 +146,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert %{"error" => "Expiry date is too soon"} = conn |> put_req_header("content-type", "application/json") - |> post("api/v1/statuses", %{ + |> post("/api/v1/statuses", %{ "status" => "oolong", "expires_in" => expires_in }) @@ -160,7 +160,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} = conn |> put_req_header("content-type", "application/json") - |> post("api/v1/statuses", %{"status" => "GNO/Linux"}) + |> post("/api/v1/statuses", %{"status" => "GNO/Linux"}) |> json_response_and_validate_schema(422) end @@ -353,7 +353,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do conn = conn |> put_req_header("content-type", "application/json") - |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) + |> post("/api/v1/statuses", %{"status" => content, "visibility" => "direct"}) assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200) assert response["visibility"] == "direct" @@ -390,7 +390,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do result = conn - |> get("api/v1/statuses/#{activity}") + |> get("/api/v1/statuses/#{activity}") assert %{ "content" => "cofe is my copilot", @@ -419,7 +419,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do result = conn - |> get("api/v1/statuses/#{activity}") + |> get("/api/v1/statuses/#{activity}") assert %{ "content" => "club mate is my wingman", @@ -1334,7 +1334,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert %{"id" => id} = conn |> put_req_header("content-type", "application/json") - |> post("api/v1/statuses", %{ + |> post("/api/v1/statuses", %{ "status" => "oolong", "expires_in" => expires_in }) @@ -1584,7 +1584,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do conn |> assign(:user, user3) |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"])) - |> get("api/v1/timelines/home") + |> get("/api/v1/timelines/home") [reblogged_activity] = json_response_and_validate_schema(conn3, 200) diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index b13a8033b..c120dd53c 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -527,7 +527,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) # Only direct should be visible here - res_conn = get(conn_user_two, "api/v1/timelines/direct") + res_conn = get(conn_user_two, "/api/v1/timelines/direct") assert [status] = json_response_and_validate_schema(res_conn, :ok) @@ -539,14 +539,14 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do build_conn() |> assign(:user, user_one) |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"])) - |> get("api/v1/timelines/direct") + |> get("/api/v1/timelines/direct") [status] = json_response_and_validate_schema(res_conn, :ok) assert %{"visibility" => "direct"} = status # Both should be visible here - res_conn = get(conn_user_two, "api/v1/timelines/home") + res_conn = get(conn_user_two, "/api/v1/timelines/home") [_s1, _s2] = json_response_and_validate_schema(res_conn, :ok) @@ -559,14 +559,14 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do }) end) - res_conn = get(conn_user_two, "api/v1/timelines/direct") + res_conn = get(conn_user_two, "/api/v1/timelines/direct") statuses = json_response_and_validate_schema(res_conn, :ok) assert length(statuses) == 20 max_id = List.last(statuses)["id"] - res_conn = get(conn_user_two, "api/v1/timelines/direct?max_id=#{max_id}") + res_conn = get(conn_user_two, "/api/v1/timelines/direct?max_id=#{max_id}") assert [status] = json_response_and_validate_schema(res_conn, :ok) @@ -591,7 +591,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do visibility: "direct" }) - res_conn = get(conn, "api/v1/timelines/direct") + res_conn = get(conn, "/api/v1/timelines/direct") [status] = json_response_and_validate_schema(res_conn, :ok) assert status["id"] == direct.id From d9f031c9da42a1bde12a37bf26298c3a3c731121 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 21:11:13 +0000 Subject: [PATCH 0241/1008] Bump minimum Elixir to 1.12 --- .gitlab-ci.yml | 13 +++++++------ Dockerfile | 2 +- ci/Dockerfile | 2 +- mix.exs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8daa9f434..2cb39a5c9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ image: git.pleroma.social:5050/pleroma/pleroma/ci-base variables: &global_variables + ELIXIR_VER: 1.12.3 POSTGRES_DB: pleroma_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -276,7 +277,7 @@ stop_review_app: amd64: stage: release - image: elixir:1.11.4 + image: elixir:$ELIXIR_VER only: &release-only - stable@pleroma/pleroma - develop@pleroma/pleroma @@ -316,7 +317,7 @@ amd64-musl: stage: release artifacts: *release-artifacts only: *release-only - image: elixir:1.11.4-alpine + image: elixir:$ELIXIR_VER-alpine tags: - amd64 cache: *release-cache @@ -334,7 +335,7 @@ arm: only: *release-only tags: - arm32-specified - image: arm32v7/elixir:1.11.4 + image: arm32v7/elixir:$ELIXIR_VER cache: *release-cache variables: *release-variables before_script: *before-release @@ -346,7 +347,7 @@ arm-musl: only: *release-only tags: - arm32-specified - image: arm32v7/elixir:1.11.4-alpine + image: arm32v7/elixir:$ELIXIR_VER-alpine cache: *release-cache variables: *release-variables before_script: *before-release-musl @@ -358,7 +359,7 @@ arm64: only: *release-only tags: - arm - image: arm64v8/elixir:1.11.4 + image: arm64v8/elixir:$ELIXIR_VER cache: *release-cache variables: *release-variables before_script: *before-release @@ -370,7 +371,7 @@ arm64-musl: only: *release-only tags: - arm - image: arm64v8/elixir:1.11.4-alpine + image: arm64v8/elixir:$ELIXIR_VER-alpine cache: *release-cache variables: *release-variables before_script: *before-release-musl diff --git a/Dockerfile b/Dockerfile index 310d18104..56c99da72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG ELIXIR_IMG=hexpm/elixir -ARG ELIXIR_VER=1.11.4 +ARG ELIXIR_VER=1.12.3 ARG ERLANG_VER=24.2.1 ARG ALPINE_VER=3.17.0 diff --git a/ci/Dockerfile b/ci/Dockerfile index ca28b7029..a2b566873 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -1,4 +1,4 @@ -FROM elixir:1.11.4 +FROM elixir:1.12.3 # Single RUN statement, otherwise intermediate images are created # https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run diff --git a/mix.exs b/mix.exs index e346834e5..6e3d69379 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule Pleroma.Mixfile do [ app: :pleroma, version: version("2.5.52"), - elixir: "~> 1.11", + elixir: "~> 1.12", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], From 2b8bbb288c26a95970e2d9e988f5eb303e052644 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 22:11:17 +0000 Subject: [PATCH 0242/1008] Phoenix.Socket.Transport.force_ssl/4 no longer exists --- lib/phoenix/transports/web_socket/raw.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex index 8cf9c32a2..cf4fda79f 100644 --- a/lib/phoenix/transports/web_socket/raw.ex +++ b/lib/phoenix/transports/web_socket/raw.ex @@ -26,7 +26,6 @@ defmodule Phoenix.Transports.WebSocket.Raw do conn |> fetch_query_params |> Transport.transport_log(opts[:transport_log]) - |> Transport.force_ssl(handler, endpoint, opts) |> Transport.check_origin(handler, endpoint, opts) case conn do From f0e5f0e8375bb55e13b0f054fcf09871d2967d24 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 May 2023 22:14:55 +0000 Subject: [PATCH 0243/1008] Fix compile warning warning: doing a prefix match with globs is deprecated, invalid segment "pleroma*path" --- lib/pleroma/web/router.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c1a690e28..6b9e158a3 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -996,8 +996,8 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web.Fallback do get("/registration/:token", RedirectController, :registration_page) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) - match(:*, "/api/pleroma*path", LegacyPleromaApiRerouterPlug, []) - get("/api*path", RedirectController, :api_not_implemented) + match(:*, "/api/pleroma/*path", LegacyPleromaApiRerouterPlug, []) + get("/api/*path", RedirectController, :api_not_implemented) get("/*path", RedirectController, :redirector_with_preload) options("/*path", RedirectController, :empty) From c9cb90ff4fac5bf645720c7e7ef0bf4b6acf8afa Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 31 May 2023 17:49:06 -0600 Subject: [PATCH 0244/1008] Media proxy base URL doesn't need /proxy --- docs/configuration/hardening.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration/hardening.md b/docs/configuration/hardening.md index 493ba608c..cc46d1ff9 100644 --- a/docs/configuration/hardening.md +++ b/docs/configuration/hardening.md @@ -68,7 +68,7 @@ It is STRONGLY RECOMMENDED to serve both the locally-uploaded media and the medi ```elixir config :pleroma, :media_proxy, - base_url: "https://some.other.domain/proxy" + base_url: "https://some.other.domain" config :pleroma, Pleroma.Upload, base_url: "https://some.other.domain/media" From 313e68c1809a281a781b221778c3e3c44728cca0 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 6 Jan 2023 21:53:17 +0100 Subject: [PATCH 0245/1008] mix: bump gettext to ~0.20 Includes https://github.com/elixir-gettext/gettext/pull/304 in 0.20.0+ Includes https://github.com/elixir-gettext/expo/issues/91 in 0.22+ via expo 0.2.0+ --- changelog.d/3831.skip | 0 mix.exs | 7 ++----- mix.lock | 3 ++- priv/gettext/en_test/LC_MESSAGES/default.po | 1 - priv/gettext/en_test/LC_MESSAGES/errors.po | 1 - priv/gettext/en_test/LC_MESSAGES/posix_errors.po | 1 - priv/gettext/en_test/LC_MESSAGES/static_pages.po | 4 ---- priv/gettext/ru/LC_MESSAGES/errors.po | 1 - priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po | 4 ---- 9 files changed, 4 insertions(+), 18 deletions(-) create mode 100644 changelog.d/3831.skip diff --git a/changelog.d/3831.skip b/changelog.d/3831.skip new file mode 100644 index 000000000..e69de29bb diff --git a/mix.exs b/mix.exs index bb16f0a9c..62ac1c1a0 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule Pleroma.Mixfile do version: version("2.5.52"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), - compilers: [:phoenix, :gettext] ++ Mix.compilers(), + compilers: [:phoenix] ++ Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], xref: [exclude: [:eldap]], start_permanent: Mix.env() == :prod, @@ -127,10 +127,7 @@ defmodule Pleroma.Mixfile do {:plug_cowboy, "~> 2.3"}, # oban 2.14 requires Elixir 1.12+ {:oban, "~> 2.13.4"}, - {:gettext, - git: "https://github.com/tusooa/gettext.git", - ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808", - override: true}, + {:gettext, "~> 0.20"}, {:bcrypt_elixir, "~> 2.2"}, {:trailing_format_plug, "~> 0.0.7"}, {:fast_sanitize, "~> 0.2.0"}, diff --git a/mix.lock b/mix.lock index 43012a58e..b6938f44b 100644 --- a/mix.lock +++ b/mix.lock @@ -42,6 +42,7 @@ "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, + "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "fast_html": {:hex, :fast_html, "2.0.5", "c61760340606c1077ff1f196f17834056cb1dd3d5cb92a9f2cabf28bc6221c3c", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "605f4f4829443c14127694ebabb681778712ceecb4470ec32aa31012330e6506"}, "fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, @@ -49,7 +50,7 @@ "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "gettext": {:git, "https://github.com/tusooa/gettext.git", "72fb2496b6c5280ed911bdc3756890e7f38a4808", [ref: "72fb2496b6c5280ed911bdc3756890e7f38a4808"]}, + "gettext": {:hex, :gettext, "0.22.2", "6bfca374de34ecc913a28ba391ca184d88d77810a3e427afa8454a71a51341ac", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "8a2d389673aea82d7eae387e6a2ccc12660610080ae7beb19452cfdc1ec30f60"}, "gun": {:hex, :gun, "2.0.0", "2326bc0fd6d9cf628419708270d6fe8b02b8d002cf992e4165a77d997b1defd0", [:make, :rebar3], [{:cowlib, "2.12.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6613cb7c62930dc8d58263c44dda72f8556346ba88358fc929dcbc5f76d04569"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, diff --git a/priv/gettext/en_test/LC_MESSAGES/default.po b/priv/gettext/en_test/LC_MESSAGES/default.po index 63db74608..037e14466 100644 --- a/priv/gettext/en_test/LC_MESSAGES/default.po +++ b/priv/gettext/en_test/LC_MESSAGES/default.po @@ -9,7 +9,6 @@ msgid "" msgstr "" "Language: en_test\n" -"Plural-Forms: nplurals=2\n" #, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:122 diff --git a/priv/gettext/en_test/LC_MESSAGES/errors.po b/priv/gettext/en_test/LC_MESSAGES/errors.po index a40de7f8b..286bbb1aa 100644 --- a/priv/gettext/en_test/LC_MESSAGES/errors.po +++ b/priv/gettext/en_test/LC_MESSAGES/errors.po @@ -9,7 +9,6 @@ msgid "" msgstr "" "Language: en_test\n" -"Plural-Forms: nplurals=2\n" msgid "can't be blank" msgstr "" diff --git a/priv/gettext/en_test/LC_MESSAGES/posix_errors.po b/priv/gettext/en_test/LC_MESSAGES/posix_errors.po index 663fc5924..6ff9dc53d 100644 --- a/priv/gettext/en_test/LC_MESSAGES/posix_errors.po +++ b/priv/gettext/en_test/LC_MESSAGES/posix_errors.po @@ -9,7 +9,6 @@ msgid "" msgstr "" "Language: en_test\n" -"Plural-Forms: nplurals=2\n" msgid "eperm" msgstr "" diff --git a/priv/gettext/en_test/LC_MESSAGES/static_pages.po b/priv/gettext/en_test/LC_MESSAGES/static_pages.po index 1a3b7b355..daf312093 100644 --- a/priv/gettext/en_test/LC_MESSAGES/static_pages.po +++ b/priv/gettext/en_test/LC_MESSAGES/static_pages.po @@ -21,10 +21,6 @@ msgstr "" #~ ## #~ ## Use "mix gettext.extract --merge" or "mix gettext.merge" #~ ## to merge POT files into PO files. -#~ msgid "" -#~ msgstr "" -#~ "Language: en_test\n" -#~ "Plural-Forms: nplurals=2\n" #, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9 diff --git a/priv/gettext/ru/LC_MESSAGES/errors.po b/priv/gettext/ru/LC_MESSAGES/errors.po index 39f83e8a6..64218da6f 100644 --- a/priv/gettext/ru/LC_MESSAGES/errors.po +++ b/priv/gettext/ru/LC_MESSAGES/errors.po @@ -9,7 +9,6 @@ msgid "" msgstr "" "Language: ru\n" -"Plural-Forms: nplurals=3\n" msgid "can't be blank" msgstr "не может быть пустым" diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po b/priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po index cbd6feb60..809b13d47 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/static_pages.po @@ -24,10 +24,6 @@ msgstr "" ## ## Use "mix gettext.extract --merge" or "mix gettext.merge" ## to merge POT files into PO files. -#~ msgid "" -#~ msgstr "" -#~ "Language: zh_Hans\n" -#~ "Plural-Forms: nplurals=1\n" #: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9 #, elixir-format From cbc5b8cebd9255e0c49e8fb02daed4680be1d336 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 2 Jun 2023 17:03:21 +0400 Subject: [PATCH 0246/1008] B Preload: Make sure that the preloaded json is html safe --- lib/pleroma/web/preload.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/preload.ex b/lib/pleroma/web/preload.ex index 4485383f9..6a4a8885e 100644 --- a/lib/pleroma/web/preload.ex +++ b/lib/pleroma/web/preload.ex @@ -11,7 +11,7 @@ defmodule Pleroma.Web.Preload do terms = params |> parser.generate_terms() - |> Enum.map(fn {k, v} -> {k, Base.encode64(Jason.encode!(v))} end) + |> Enum.map(fn {k, v} -> {k, Base.encode64(Jason.encode!(v, escape: :html_safe))} end) |> Enum.into(%{}) Map.merge(acc, terms) @@ -19,7 +19,7 @@ defmodule Pleroma.Web.Preload do rendered_html = preload_data - |> Jason.encode!() + |> Jason.encode!(escape: :html_safe) |> build_script_tag() |> HTML.safe_to_string() From 40d40d67a3cee4d57f9200d0980df1b21d08a834 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 2 Jun 2023 17:09:23 +0400 Subject: [PATCH 0247/1008] Add changelog. --- changelog.d/3901.security | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3901.security diff --git a/changelog.d/3901.security b/changelog.d/3901.security new file mode 100644 index 000000000..a3d8bd01f --- /dev/null +++ b/changelog.d/3901.security @@ -0,0 +1 @@ +Preload: Make generated JSON html-safe. It already was html safe because it only consists of config data that is base64 encoded, but this will keep it safe it that ever changes. From 2e45be2653f662f60e87fce56aa3c256e20bd1fb Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 2 Jun 2023 11:34:04 -0400 Subject: [PATCH 0248/1008] Add :phoenix to extra_applications to suppress a warning Related to use of Phoenix.Flash.get/2 --- mix.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/mix.exs b/mix.exs index 6e3d69379..771b491b8 100644 --- a/mix.exs +++ b/mix.exs @@ -78,6 +78,7 @@ defmodule Pleroma.Mixfile do :comeonin, :fast_sanitize, :os_mon, + :phoenix, :ssl ], included_applications: [:ex_syslogger] From bcd7ccac11f3f7217ffd8ec32d32457436315f58 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 3 Jun 2023 14:03:51 -0400 Subject: [PATCH 0249/1008] Support a type called "change" --- tools/check-changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check-changelog b/tools/check-changelog index 60692033f..64e59ae7f 100644 --- a/tools/check-changelog +++ b/tools/check-changelog @@ -6,7 +6,7 @@ git remote add upstream https://git.pleroma.social/pleroma/pleroma.git git fetch upstream ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}:refs/remotes/upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME git diff --raw --no-renames upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \ - grep ' A\t' | grep '\.\(skip\|add\|remove\|fix\|security\)$' + grep ' A\t' | grep '\.\(skip\|add\|remove\|fix\|security|change\)$' ret=$? if [ $ret -eq 0 ]; then From c665d532951c34c7d1185e66b59390202b54d0c9 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 3 Jun 2023 14:04:12 -0400 Subject: [PATCH 0250/1008] Update to Phoenix 1.7 --- changelog.d/3900.change | 1 + changelog.d/3900.skip | 0 2 files changed, 1 insertion(+) create mode 100644 changelog.d/3900.change delete mode 100644 changelog.d/3900.skip diff --git a/changelog.d/3900.change b/changelog.d/3900.change new file mode 100644 index 000000000..fe0cc2fbf --- /dev/null +++ b/changelog.d/3900.change @@ -0,0 +1 @@ +Update to Phoenix 1.7 diff --git a/changelog.d/3900.skip b/changelog.d/3900.skip deleted file mode 100644 index e69de29bb..000000000 From 63ef1dcedca7fb315f611388e477d3847d1acaa1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 3 Jun 2023 14:17:49 -0400 Subject: [PATCH 0251/1008] Phoenix.Router.routes/1 is the public function we are meant to be using here --- lib/pleroma/web/router.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6b9e158a3..a6e180c4c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -1003,9 +1003,8 @@ defmodule Pleroma.Web.Router do options("/*path", RedirectController, :empty) end - # TODO: Change to Phoenix.Router.routes/1 for Phoenix 1.6.0+ def get_api_routes do - __MODULE__.__routes__() + Phoenix.Router.routes(__MODULE__) |> Enum.reject(fn r -> r.plug == Pleroma.Web.Fallback.RedirectController end) |> Enum.map(fn r -> r.path From fadcd7f1a9e41ec9b54f251cf782688bf3d36889 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 7 Jun 2023 09:19:22 -0400 Subject: [PATCH 0252/1008] Revert MediaProxy Host header validation Something is going wrong here even though the tests are correct. --- changelog.d/3896.add | 2 +- .../web/media_proxy/media_proxy_controller.ex | 25 ---------------- .../media_proxy_controller_test.exs | 29 ------------------- test/support/conn_case.ex | 5 +--- 4 files changed, 2 insertions(+), 59 deletions(-) diff --git a/changelog.d/3896.add b/changelog.d/3896.add index 36d8286ff..e8fde4c8a 100644 --- a/changelog.d/3896.add +++ b/changelog.d/3896.add @@ -1 +1 @@ -Validate Host header for MediaProxy and Uploads and return a 302 if the base_url has changed +Validate Host header for Uploads and return a 302 if the base_url has changed diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex index 20f3a3438..bda5b36ed 100644 --- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex +++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex @@ -12,7 +12,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do alias Pleroma.Web.MediaProxy alias Plug.Conn - plug(:validate_host) plug(:sandbox) def remote(conn, %{"sig" => sig64, "url" => url64}) do @@ -206,30 +205,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do Config.get([:media_proxy, :proxy_opts], []) end - defp validate_host(conn, _params) do - %{scheme: proxy_scheme, host: proxy_host, port: proxy_port} = - MediaProxy.base_url() |> URI.parse() - - if match?(^proxy_host, conn.host) do - conn - else - redirect_url = - %URI{ - scheme: proxy_scheme, - host: proxy_host, - port: proxy_port, - path: conn.request_path, - query: conn.query_string - } - |> URI.to_string() - |> String.trim_trailing("?") - - conn - |> Phoenix.Controller.redirect(external: redirect_url) - |> halt() - end - end - defp sandbox(conn, _params) do conn |> merge_resp_headers([{"content-security-policy", "sandbox;"}]) diff --git a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs index deb407709..9ce092fd8 100644 --- a/test/pleroma/web/media_proxy/media_proxy_controller_test.exs +++ b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs @@ -54,35 +54,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do } = get(conn, "/proxy/hhgfh/eeee/fff") end - test "it returns a 302 for invalid host", %{conn: conn} do - new_proxy_base = "http://mp.localhost/" - - %{scheme: new_proxy_scheme, host: new_proxy_host, port: new_proxy_port} = - URI.parse(new_proxy_base) - - clear_config([:media_proxy, :base_url], new_proxy_base) - - proxy_url = - MediaProxy.encode_url("https://pleroma.social/logo.jpeg") - |> URI.parse() - |> Map.put(:host, "wronghost") - |> URI.to_string() - - expected_url = - URI.parse(proxy_url) - |> Map.put(:host, new_proxy_host) - |> Map.put(:port, new_proxy_port) - |> Map.put(:scheme, new_proxy_scheme) - |> URI.to_string() - - with_mock Pleroma.ReverseProxy, - call: fn _conn, _url, _opts -> %Conn{status: :success} end do - conn = get(conn, proxy_url) - - assert redirected_to(conn, 302) == expected_url - end - end - test "redirects to valid url when filename is invalidated", %{conn: conn, url: url} do invalid_url = String.replace(url, "test.png", "test-file.png") response = get(conn, invalid_url) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index c1cb0295b..f010fec33 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -120,9 +120,6 @@ defmodule Pleroma.Web.ConnCase do Mox.verify_on_exit!() - {:ok, - conn: - Phoenix.ConnTest.build_conn() - |> Map.put(:host, Pleroma.Web.Endpoint.host())} + {:ok, conn: Phoenix.ConnTest.build_conn()} end end From 1ca1b4b32f2cba5f78a2d55645b8d2814bbfc593 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 7 Jun 2023 09:25:57 -0400 Subject: [PATCH 0253/1008] changelog.d --- changelog.d/3902.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/3902.skip diff --git a/changelog.d/3902.skip b/changelog.d/3902.skip new file mode 100644 index 000000000..e69de29bb From 10dfa107d5d5d3b6fa7c13eb005c4cd5b6b94661 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 11 Jun 2023 16:22:03 +0400 Subject: [PATCH 0254/1008] Update changelog --- changelog.d/3874.remove | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3874.remove diff --git a/changelog.d/3874.remove b/changelog.d/3874.remove new file mode 100644 index 000000000..a81f744bf --- /dev/null +++ b/changelog.d/3874.remove @@ -0,0 +1 @@ +Remove a few unused indexes. From 22878cf84afd3f8b844c66e99f37022318c4c55e Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 11 Jun 2023 16:22:16 +0400 Subject: [PATCH 0255/1008] B Migrations: Don't remove activity_visibility_index for now. --- .../20230422154018_drop_unused_indexes.exs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/priv/repo/migrations/20230422154018_drop_unused_indexes.exs b/priv/repo/migrations/20230422154018_drop_unused_indexes.exs index f2ba6b8df..0d877a798 100644 --- a/priv/repo/migrations/20230422154018_drop_unused_indexes.exs +++ b/priv/repo/migrations/20230422154018_drop_unused_indexes.exs @@ -22,13 +22,6 @@ defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do drop_if_exists( index(:activities, ["((data #> '{\"object\",\"likes\"}'))"], name: :activities_likes) ) - - drop_if_exists( - index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC NULLS LAST"], - name: :activities_visibility_index, - where: "data->>'type' = 'Create'" - ) - ) end def down do @@ -76,13 +69,5 @@ defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do concurrently: true ) ) - - create_if_not_exists( - index(:activities, ["activity_visibility(actor, recipients, data)", "id DESC NULLS LAST"], - name: :activities_visibility_index, - where: "data->>'type' = 'Create'", - concurrently: true - ) - ) end end From 6611c6ce4ef3fe76bc9a5eb53677d3b6cde22793 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 11 Jun 2023 16:45:31 +0400 Subject: [PATCH 0256/1008] B ForceMentionsInContent: Fix test, refactor. --- .../web/activity_pub/mrf/force_mentions_in_content.ex | 8 +++++--- .../activity_pub/mrf/force_mentions_in_content_test.exs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex index b9b175cc3..5532093cb 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex @@ -95,11 +95,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do |> Enum.reject(&is_nil/1) |> sort_replied_user(replied_to_user) - explicitly_mentioned_uris = extract_mention_uris_from_content(content) + explicitly_mentioned_uris = + extract_mention_uris_from_content(content) + |> MapSet.new() added_mentions = - Enum.reduce(mention_users, "", fn %User{ap_id: api_id, uri: uri} = user, acc -> - unless Enum.any?([api_id, uri], fn u -> u in explicitly_mentioned_uris end) do + Enum.reduce(mention_users, "", fn %User{ap_id: ap_id, uri: uri} = user, acc -> + if MapSet.disjoint?(MapSet.new([ap_id, uri]), explicitly_mentioned_uris) do acc <> Formatter.mention_from_user(user, %{mentions_format: :compact}) <> " " else acc diff --git a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs index 5ebd972a6..811ef105c 100644 --- a/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs +++ b/test/pleroma/web/activity_pub/mrf/force_mentions_in_content_test.exs @@ -282,7 +282,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContentTest do {:ok, post} = CommonAPI.post(rogerick, %{status: "eugh"}) inline_mentions = [ - "@rogerick", + "@rogerick", "@greg" ] From a663b73634ddf355827051cc139c0159f8eb794b Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 13 Jun 2023 12:45:18 +0200 Subject: [PATCH 0257/1008] Add no_new_privs to OpenRC service files --- changelog.d/no_new_privs.add | 1 + installation/init.d/pleroma | 1 + rel/files/installation/init.d/pleroma | 1 + 3 files changed, 3 insertions(+) create mode 100644 changelog.d/no_new_privs.add diff --git a/changelog.d/no_new_privs.add b/changelog.d/no_new_privs.add new file mode 100644 index 000000000..b67396a4b --- /dev/null +++ b/changelog.d/no_new_privs.add @@ -0,0 +1 @@ +(hardening) Add no_new_privs=yes to OpenRC service files diff --git a/installation/init.d/pleroma b/installation/init.d/pleroma index 384536f7e..cb6635a0b 100755 --- a/installation/init.d/pleroma +++ b/installation/init.d/pleroma @@ -8,6 +8,7 @@ pidfile="/var/run/pleroma.pid" directory=/opt/pleroma healthcheck_delay=60 healthcheck_timer=30 +no_new_privs="yes" : ${pleroma_port:-4000} diff --git a/rel/files/installation/init.d/pleroma b/rel/files/installation/init.d/pleroma index dea1db26c..ca5b842e1 100755 --- a/rel/files/installation/init.d/pleroma +++ b/rel/files/installation/init.d/pleroma @@ -9,6 +9,7 @@ command=/opt/pleroma/bin/pleroma command_args="start" command_user=pleroma command_background=1 +no_new_privs="yes" # Ask process to terminate within 30 seconds, otherwise kill it retry="SIGTERM/30/SIGKILL/5" From d5a7079f42228980471b6210aaaea69fcaa4c9ed Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 13 Jun 2023 15:39:20 +0200 Subject: [PATCH 0258/1008] media_graphics_packages.md: Fix markdown syntax --- .../optional/media_graphics_packages.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/installation/optional/media_graphics_packages.md b/docs/installation/optional/media_graphics_packages.md index de402d1c4..ad01d47d1 100644 --- a/docs/installation/optional/media_graphics_packages.md +++ b/docs/installation/optional/media_graphics_packages.md @@ -1,9 +1,10 @@ # Optional software packages needed for specific functionality For specific Pleroma functionality (which is disabled by default) some or all of the below packages are required: - * `ImageMagic` - * `ffmpeg` - * `exiftool` + +* `ImageMagic` +* `ffmpeg` +* `exiftool` Please refer to documentation in `docs/installation` on how to install them on specific OS. @@ -14,20 +15,23 @@ Note: the packages are not required with the current default settings of Pleroma `ImageMagick` is a set of tools to create, edit, compose, or convert bitmap images. It is required for the following Pleroma features: - * `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`) - * Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`) + +* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`) +* Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`) ## `ffmpeg` `ffmpeg` is software to record, convert and stream audio and video. It is required for the following Pleroma features: - * Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`) + +* Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`) ## `exiftool` `exiftool` is media files metadata reader/writer. It is required for the following Pleroma features: - * `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`) - * `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`) + +* `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`) +* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`) From 4392fff212b42204990569f396a69e74b053e5ca Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 13 Jun 2023 15:40:12 +0200 Subject: [PATCH 0259/1008] otp_vs_from_source*: Acknowledge distro packages --- docs/installation/otp_vs_from_source.include | 9 +++++++-- docs/installation/otp_vs_from_source_source.include | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/installation/otp_vs_from_source.include b/docs/installation/otp_vs_from_source.include index 63e837a53..6c7820275 100644 --- a/docs/installation/otp_vs_from_source.include +++ b/docs/installation/otp_vs_from_source.include @@ -1,3 +1,8 @@ -## OTP releases vs from-source installations +## Packaged (OTP) installation vs Manual (from-source) installations -There are two ways to install Pleroma. You can use OTP releases or do a from-source installation. OTP releases are as close as you can get to binary releases with Erlang/Elixir. The release is self-contained, and provides everything needed to boot it, it is easily administered via the provided shell script to open up a remote console, start/stop/restart the release, start in the background, send remote commands, and more. With from source installations you install Pleroma from source, meaning you have to install certain dependencies like Erlang+Elixir and compile Pleroma yourself. +There is multiple ways to install Pleroma. +
+
Distro-provided packages
This is the recommended method, where you can get the strongest compatibility guarantees and the best dependency-management
+
Pleroma-provided OTP binaries
Intended as fallback for Alpine/Debian-compatible systems lacking a proper Pleroma package, they are heavier than proper distro packages as they also contain Erlang/Elixir and can break after system updates
+
Manual from-source installation
Needs build-dependencies to be installed and manual updates+rebuilds. Allows for easier source-customisations.
+
diff --git a/docs/installation/otp_vs_from_source_source.include b/docs/installation/otp_vs_from_source_source.include index 63482b69d..9f7134229 100644 --- a/docs/installation/otp_vs_from_source_source.include +++ b/docs/installation/otp_vs_from_source_source.include @@ -1,3 +1,3 @@ {! backend/installation/otp_vs_from_source.include !} -This guide covers a from-source installation. To install using OTP releases, please check out [the OTP guide](./otp_en.md). +This guide covers a manual from-source installation. To install using OTP releases, please check for the presence of a distro package, failing that you can use [Pleroma-provided OTP binaries](./otp_en.md). From fb19f0d844eae80ff06015419119f7a709e5095f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 13 Jun 2023 15:40:48 +0200 Subject: [PATCH 0260/1008] gentoo_otp_en: Add packaged installation documentation --- docs/installation/gentoo_otp_en.md | 207 +++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 docs/installation/gentoo_otp_en.md diff --git a/docs/installation/gentoo_otp_en.md b/docs/installation/gentoo_otp_en.md new file mode 100644 index 000000000..5dcfe0b7b --- /dev/null +++ b/docs/installation/gentoo_otp_en.md @@ -0,0 +1,207 @@ +# Packaged install on Gentoo Linux + +{! backend/installation/otp_vs_from_source.include !} + +A [manual installation guide for gentoo](./gentoo_en.md) is also available. + +## Installation + +This guide will assume that you have administrative rights, either as root or a user with [sudo permissions](https://wiki.gentoo.org/wiki/Sudo). Lines that begin with `#` indicate that they should be run as the superuser. Lines using `$` should be run as the indicated user, e.g. `pleroma$` should be run as the `pleroma` user. + +{! backend/installation/generic_dependencies.include !} + +### Installing a cron daemon + +Gentoo quite pointedly does not come with a cron daemon installed, and as such it is recommended you install one to automate certbot renewals and to allow other system administration tasks to be run automatically. Gentoo has [a whole wide world of cron options](https://wiki.gentoo.org/wiki/Cron) but if you just want A Cron That Works, `emerge --ask virtual/cron` will install the default cron implementation (probably cronie) which will work just fine. For the purpouses of this guide, we will be doing just that. + +### Required ebuilds + +* `www-apps/pleroma` + +#### Optional ebuilds used in this guide + +* `www-servers/nginx` (preferred, example configs for other reverse proxies can be found in the repo) +* `app-crypt/certbot` (or any other ACME client for Let’s Encrypt certificates) +* `app-crypt/certbot-nginx` (nginx certbot plugin that allows use of the all-powerful `--nginx` flag on certbot) +* `media-gfx/imagemagick` +* `media-video/ffmpeg` +* `media-libs/exiftool` + +### Prepare the system + +* If you haven't yet done so, add the [Gentoo User Repository (GURU)](https://wiki.gentoo.org/wiki/Project:GURU), where the `www-apps/pleroma` ebuild currently lives at: +```shell + # eselect repository enable guru +``` + +* Ensure that you have the latest copy of the Gentoo and GURU ebuilds if you have not synced them yet: + +```shell + # emaint sync -a +``` + + +* Emerge all required the required and suggested software in one go: + +```shell + # emerge --ask www-apps/pleroma www-servers/nginx app-crypt/certbot app-crypt/certbot-nginx +``` + +If you would not like to install the optional packages, remove them from this line. + +If you're running this from a low-powered virtual machine, it should work though it will take some time. There were no issues on a VPS with a single core and 1GB of RAM; if you are using an even more limited device and run into issues, you can try creating a swapfile or use a more powerful machine running Gentoo to [cross build](https://wiki.gentoo.org/wiki/Cross_build_environment). If you have a wait ahead of you, now would be a good time to take a break, strech a bit, refresh your beverage of choice and/or get a snack, and reply to Arch users' posts with "I use Gentoo btw" as we do. + +### Setup PostgreSQL + +[Gentoo Wiki article](https://wiki.gentoo.org/wiki/PostgreSQL) as well as [PostgreSQL QuickStart](https://wiki.gentoo.org/wiki/PostgreSQL/QuickStart) might be worth a quick glance, as the way Gentoo handles postgres is slightly unusual, with built in capability to have two different databases running for testing and live or whatever other purpouse. While it is still straightforward to install, it does mean that the version numbers used in this guide might change for future updates, so keep an eye out for the output you get from `emerge` to ensure you are using the correct ones. + +* Initialize the database cluster + +The output from emerging postgresql should give you a command for initializing the postgres database. The default slot should be indicated in this command, ensure that it matches the command below. + +```shell + # emerge --config dev-db/postgresql:11 +``` + +### Install media / graphics packages (optional) + +See [Optional software packages needed for specific functionality](docs/installation/optional/media_graphics_packages.md) for details. + +```shell +# emerge --ask media-video/ffmpeg media-gfx/imagemagick media-libs/exiftool +``` + +### Setup PleromaBE + +* Generate the configuration: + +```shell + # pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql" +``` + +* Create the PostgreSQL database + +```shell + # sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql" +``` + +* Now run the database migration: + +```shell + # pleroma_ctl migrate +``` + +* Optional: If you have installed RUM indexes (`dev-db/rum`) you also need to run: +``` + # sudo -Hu pleroma "pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" +``` + +* Now you can start Pleroma already and add it in the default runlevel + +```shell + # rc-service pleroma start + # rc-update add pleroma default +``` + +It probably won't work over the public internet quite yet, however, as we still need to set up a web server to proxy to the pleroma application, as well as configure SSL. + +### Finalize installation + +Assuming you want to open your newly installed federated social network to, well, the federation, you should run nginx or some other webserver/proxy in front of Pleroma. It is also a good idea to set up Pleroma to run as a system service. + +#### Nginx + +* Install nginx, if not already done: + +```shell + # emerge --ask www-servers/nginx +``` + +* Create directories for available and enabled sites: + +```shell + # mkdir -p /etc/nginx/sites-{available,enabled} +``` + +* Append the following line at the end of the `http` block in `/etc/nginx/nginx.conf`: + +```Nginx +include sites-enabled/*; +``` + +* Setup your SSL cert, using your method of choice or certbot. If using certbot, install it if you haven't already: + +```shell + # emerge --ask app-crypt/certbot app-crypt/certbot-nginx +``` + +and then set it up: + +```shell + # mkdir -p /var/lib/letsencrypt/ + # certbot certonly --email -d --standalone +``` + +If that doesn't work the first time, add `--dry-run` to further attempts to avoid being ratelimited as you identify the issue, and do not remove it until the dry run succeeds. If that doesn’t work, make sure, that nginx is not already running. If it still doesn’t work, try setting up nginx first (change ssl “on” to “off” and try again). Often the answer to issues with certbot is to use the `--nginx` flag once you have nginx up and running. + +If you are using any additional subdomains, such as for a media proxy, you can re-run the same command with the subdomain in question. When it comes time to renew later, you will not need to run multiple times for each domain, one renew will handle it. + +--- + +* Copy the example nginx configuration and activate it: + +```shell + # cp /opt/pleroma/installation/pleroma.nginx /etc/nginx/sites-available/ + # ln -s /etc/nginx/sites-available/pleroma.nginx /etc/nginx/sites-enabled/pleroma.nginx +``` + +* Take some time to ensure that your nginx config is correct + +Replace all instances of `example.tld` with your instance's public URL. If for whatever reason you made changes to the port that your pleroma app runs on, be sure that is reflected in your configuration. + +Pay special attention to the line that begins with `ssl_ecdh_curve`. It is stongly advised to comment that line out so that OpenSSL will use its full capabilities, and it is also possible you are running OpenSSL 1.0.2 necessitating that you do this. + +* Enable and start nginx: + +```shell + # rc-update add nginx default + # /etc/init.d/nginx start +``` + +If you are using certbot, it is HIGHLY recommend you set up a cron job that renews your certificate, and that you install the suggested `certbot-nginx` plugin. If you don't do these things, you only have yourself to blame when your instance breaks suddenly because you forgot about it. + +First, ensure that the command you will be installing into your crontab works. + +```shell + # /usr/bin/certbot renew --nginx +``` + +Assuming not much time has passed since you got certbot working a few steps ago, you should get a message for all domains you installed certificates for saying `Cert not yet due for renewal`. + +Now, run crontab as a superuser with `crontab -e` or `sudo crontab -e` as appropriate, and add the following line to your cron: + +```cron +0 0 1 * * /usr/bin/certbot renew --nginx +``` + +This will run certbot on the first of the month at midnight. If you'd rather run more frequently, it's not a bad idea, feel free to go for it. + +#### Other webserver/proxies + +If you would like to use other webservers or proxies, there are example configurations for some popular alternatives in `/opt/pleroma/installation/`. You can, of course, check out [the Gentoo wiki](https://wiki.gentoo.org) for more information on installing and configuring said alternatives. + +#### Create your first user + +If your instance is up and running, you can create your first user with administrative rights with the following task: + +```shell +pleroma$ pleroma_ctl user new --admin +``` + +#### Further reading + +{! backend/installation/further_reading.include !} + +## Questions + +Questions about the installation or didn’t it work as it should be, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC. From eddfd41c1f2fb15f5a28b5474a64bed7b86d62d7 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 13 Jun 2023 15:41:09 +0200 Subject: [PATCH 0261/1008] gentoo_en: Reference packaged installation --- docs/installation/gentoo_en.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md index 36882c8c8..02a3c709b 100644 --- a/docs/installation/gentoo_en.md +++ b/docs/installation/gentoo_en.md @@ -1,6 +1,8 @@ -# Installing on Gentoo GNU/Linux +# Manual install on Gentoo GNU/Linux -{! backend/installation/otp_vs_from_source_source.include !} +{! backend/installation/otp_vs_from_source.include !} + +This guide covers a manual from-source installation. To use the gentoo package, please check the [packaged installation guide for gentoo](./gentoo.en.md). ## Installation From 937fa36ec4ed8f8aa6e2e608003260bc7c5d8d57 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 13 Jun 2023 15:47:48 +0200 Subject: [PATCH 0262/1008] changelog.d/gentoo_otp.skip: Doc-only MR --- changelog.d/gentoo_otp.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/gentoo_otp.skip diff --git a/changelog.d/gentoo_otp.skip b/changelog.d/gentoo_otp.skip new file mode 100644 index 000000000..e69de29bb From 8fa435f370cd1a85a37eae011d9db604256fbef1 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 14 Jun 2023 21:48:10 +0000 Subject: [PATCH 0263/1008] Add "potentially outdated" notice in non-English versions --- docs/installation/debian_based_jp.md | 3 +++ docs/installation/openbsd_fi.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md index 3736e857f..2489e3473 100644 --- a/docs/installation/debian_based_jp.md +++ b/docs/installation/debian_based_jp.md @@ -1,4 +1,7 @@ # Pleromaの入れ方 + +Note: This article is potentially outdated because at this time we may not have people who can speak this language well enough to update it. To see the up-to-date version, which may have significant differences or important caveats of the installation process, look up the English version. + ## 日本語訳について この記事は [Installing on Debian based distributions](Installing on Debian based distributions) の日本語訳です。何かがおかしいと思ったら、原文を見てください。 diff --git a/docs/installation/openbsd_fi.md b/docs/installation/openbsd_fi.md index 3c40b2d1a..73aca3a6f 100644 --- a/docs/installation/openbsd_fi.md +++ b/docs/installation/openbsd_fi.md @@ -1,5 +1,7 @@ # Pleroman asennus OpenBSD:llä +Note: This article is potentially outdated because at this time we may not have people who can speak this language well enough to update it. To see the up-to-date version, which may have significant differences or important caveats of the installation process, look up the English version. + Tarvitset: * Oman domainin * OpenBSD 6.3 -serverin From a5a354a36e144c19ce3f9e79cb898227fc7ef723 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 21 Jun 2023 23:10:56 -0600 Subject: [PATCH 0264/1008] Prevent bypassing authorized fetch mode with a json file --- lib/pleroma/web/plugs/http_signature_plug.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex index 4bf325218..e814efc2c 100644 --- a/lib/pleroma/web/plugs/http_signature_plug.ex +++ b/lib/pleroma/web/plugs/http_signature_plug.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do end def call(conn, _opts) do - if get_format(conn) == "activity+json" do + if get_format(conn) in ["json", "activity+json"] do conn |> maybe_assign_valid_signature() |> maybe_require_signature() From 994bfc4c095fae07cd1c61b0f91c80ec60080138 Mon Sep 17 00:00:00 2001 From: Sean King Date: Wed, 21 Jun 2023 23:13:16 -0600 Subject: [PATCH 0265/1008] Add changelog entry --- changelog.d/prevent-bypassing-authorized-fetch-mode.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/prevent-bypassing-authorized-fetch-mode.fix diff --git a/changelog.d/prevent-bypassing-authorized-fetch-mode.fix b/changelog.d/prevent-bypassing-authorized-fetch-mode.fix new file mode 100644 index 000000000..12f7260d7 --- /dev/null +++ b/changelog.d/prevent-bypassing-authorized-fetch-mode.fix @@ -0,0 +1 @@ +Prevent using the .json format to bypass authorized fetch mode \ No newline at end of file From 8bf8906045d15254dd536b97bdaf04bc772e6fa6 Mon Sep 17 00:00:00 2001 From: weblate-extractor Date: Thu, 22 Jun 2023 06:09:47 +0000 Subject: [PATCH 0266/1008] Extract translatable strings --- priv/gettext/config_descriptions.pot | 2052 +++++++++++++------------- priv/gettext/default.pot | 68 +- priv/gettext/errors.pot | 166 +-- priv/gettext/oauth_scopes.pot | 108 +- priv/gettext/static_pages.pot | 182 +-- 5 files changed, 1232 insertions(+), 1344 deletions(-) diff --git a/priv/gettext/config_descriptions.pot b/priv/gettext/config_descriptions.pot index 53b81fa41..4f60e1c85 100644 --- a/priv/gettext/config_descriptions.pot +++ b/priv/gettext/config_descriptions.pot @@ -10,6038 +10,5966 @@ msgid "" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 -msgctxt "config description at :esshd" -msgid "Before enabling this you must add :esshd to mix.exs as one of the extra_applications and generate host keys in your priv dir with ssh-keygen -m PEM -N \"\" -b 2048 -t rsa -f ssh_host_rsa_key" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 msgctxt "config description at :logger" msgid "Logger-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :mime" msgid "Mime Types settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma" msgid "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma" msgid "Authenticator" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :cors_plug" msgid "CORS plug config" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 -msgctxt "config label at :esshd" -msgid "ESSHD" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 msgctxt "config label at :logger" msgid "Logger" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :mime" msgid "Mime Types" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma" msgid "Pleroma Admin Token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma" msgid "Pleroma Authenticator" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:console" msgid "Console logger settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger" msgid "ExSyslogger-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub" msgid "ActivityPub-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets" msgid "This section configures assets to be used with various frontends. Currently the only option relates to mascots on the mastodon frontend" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth" msgid "Authentication / authorization settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool" msgid "Advanced settings for `Gun` connections pool" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications" msgid "Email notifications settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:features" msgid "Customizable features" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed" msgid "Configure feed rendering" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations" msgid "This form can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for pleroma_fe are configured. If you want to add your own configuration your settings all fields must be complete." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends" msgid "Installed frontends management" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:gopher" msgid "Gopher settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools" msgid "Advanced settings for `Hackney` connections pools" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http" msgid "HTTP settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security" msgid "HTTP security settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance" msgid "Instance-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instances_favicons" msgid "Control favicons for instances" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap" msgid "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password will be verified by trying to authenticate (bind) to a LDAP server. If a user exists in the LDAP directory but there is no account with the same name yet on the Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:majic_pool" msgid "Majic/libmagic configuration" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:manifest" msgid "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_preview_proxy" msgid "Media preview proxy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy" msgid "Media proxy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:modules" msgid "Custom Runtime Modules" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf" msgid "General MRF settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_activity_expiration" msgid "Adds automatic expiration to all local activities" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_follow_bot" msgid "Automatically follows newly discovered accounts." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hashtag" msgid "Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)\n\nNote: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hellthread" msgid "Block messages with excessive user mentions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_keyword" msgid "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_mention" msgid "Block messages which mention a specific user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_normalize_markup" msgid "MRF NormalizeMarkup settings. Scrub configured hypertext markup." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_object_age" msgid "Rejects or delists posts based on their timestamp deviance from your server's clock." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_rejectnonpublic" msgid "RejectNonPublic drops posts with non-public visibility settings." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple" msgid "Simple ingress policies" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_steal_emoji" msgid "Steals emojis from selected instances when it sees them." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_subchain" msgid "This policy processes messages through an alternate pipeline when a given message matches certain criteria. All criteria are configured as a map of regular expressions to lists of policy modules." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_vocabulary" msgid "Filter messages which belong to certain activity vocabularies" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:oauth2" msgid "Configure OAuth 2 provider capabilities" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools" msgid "Advanced settings for `Gun` workers pools" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:populate_hashtags_table" msgid "`populate_hashtags_table` background migration settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit" msgid "Rate limit settings. This is an advanced feature enabled only for :authentication by default." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated" msgid "Disallow viewing timelines, user profiles and statuses for unauthenticated users." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media" msgid "If enabled the instance will parse metadata from attached links to generate link previews" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:shout" msgid "Pleroma shout settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:static_fe" msgid "Render profiles and posts using server-generated HTML that is viewable without using JavaScript" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:streamer" msgid "Settings for notifications streamer" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:uri_schemes" msgid "URI schemes related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:web_cache_ttl" msgid "The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome" msgid "Welcome messages settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:workers" msgid "Includes custom worker options not interpretable directly by `Oban`" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter" msgid "Limits configuration for background tasks." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban" msgid "[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha" msgid "Captcha-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "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 (https://captcha.kotobank.ch) is hosted by the developer." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer" msgid "Mailer-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "New users admin email digest" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail" msgid "Email template settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter" msgid "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.ScheduledActivity" msgid "Scheduled activities settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload" msgid "Upload general settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" msgid "Filter replaces the filename of the upload" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Uploads mogrify filter settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.Local" msgid "Local uploader-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.S3" msgid "S3 uploader-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.User.Backup" msgid "Account Backup" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http" msgid "HTTP invalidate settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script" msgid "Invalidation script settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Metadata" msgid "Metadata-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp" msgid "`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.\n**If your instance is not behind at least one reverse proxy, you should not enable this plug.**\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Preload" msgid "Preload-related settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Expired activities settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter" msgid "Prometheus app metrics endpoint configuration" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :web_push_encryption-:vapid_details" msgid "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :ex_aws-:s3" msgid "S3" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:console" msgid "Console Logger" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:ex_syslogger" msgid "ExSyslogger" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub" msgid "ActivityPub" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:assets" msgid "Assets" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:auth" msgid "Auth" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:connections_pool" msgid "Connections pool" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications" msgid "Email notifications" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji" msgid "Emoji" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:features" msgid "Features" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:feed" msgid "Feed" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations" msgid "Frontend configurations" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends" msgid "Frontends" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:gopher" msgid "Gopher" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools" msgid "Hackney pools" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http" msgid "HTTP" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security" msgid "HTTP security" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance" msgid "Instance" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instances_favicons" msgid "Instances favicons" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap" msgid "LDAP" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:majic_pool" msgid "Majic pool" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:manifest" msgid "Manifest" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup" msgid "Markup Settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy" msgid "Media preview proxy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy" msgid "Media proxy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:modules" msgid "Modules" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf" msgid "MRF" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_activity_expiration" msgid "MRF Activity Expiration Policy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_follow_bot" msgid "MRF FollowBot Policy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hashtag" msgid "MRF Hashtag" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hellthread" msgid "MRF Hellthread" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_keyword" msgid "MRF Keyword" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_mention" msgid "MRF Mention" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_normalize_markup" msgid "MRF Normalize Markup" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_object_age" msgid "MRF Object Age" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_rejectnonpublic" msgid "MRF Reject Non Public" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple" msgid "MRF Simple" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_steal_emoji" msgid "MRF Emojis" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_subchain" msgid "MRF Subchain" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_vocabulary" msgid "MRF Vocabulary" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:oauth2" msgid "OAuth2" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools" msgid "Pools" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:populate_hashtags_table" msgid "Populate hashtags table" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit" msgid "Rate limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated" msgid "Restrict Unauthenticated" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media" msgid "Rich media" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:shout" msgid "Shout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:static_fe" msgid "Static FE" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:streamer" msgid "Streamer" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:uri_schemes" msgid "URI Schemes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:user" msgid "User" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:web_cache_ttl" msgid "Web cache TTL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome" msgid "Welcome" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:workers" msgid "Workers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter" msgid "ConcurrentLimiter" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban" msgid "Oban" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha" msgid "Pleroma.Captcha" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "Pleroma.Captcha.Kocaptcha" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer" msgid "Pleroma.Emails.Mailer" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "Pleroma.Emails.NewUsersDigestEmail" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail" msgid "Pleroma.Emails.UserEmail" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter" msgid "Linkify" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.ScheduledActivity" msgid "Pleroma.ScheduledActivity" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload" msgid "Pleroma.Upload" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" msgid "Pleroma.Upload.Filter.AnonymizeFilename" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Pleroma.Upload.Filter.Mogrify" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.Local" msgid "Pleroma.Uploaders.Local" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.S3" msgid "Pleroma.Uploaders.S3" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User" msgid "Pleroma.User" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User.Backup" msgid "Pleroma.User.Backup" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate" msgid "Pleroma.Web.ApiSpec.CastAndValidate" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http" msgid "Pleroma.Web.MediaProxy.Invalidation.Http" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script" msgid "Pleroma.Web.MediaProxy.Invalidation.Script" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Metadata" msgid "Pleroma.Web.Metadata" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp" msgid "Pleroma.Web.Plugs.RemoteIp" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Preload" msgid "Pleroma.Web.Preload" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Pleroma.Workers.PurgeExpiredActivity" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter" msgid "Pleroma.Web.Endpoint.MetricsExporter" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :web_push_encryption-:vapid_details" msgid "Vapid Details" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 -msgctxt "config description at :esshd > :enabled" -msgid "Enables SSH" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config description at :esshd > :handler" -msgid "Handler module" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config description at :esshd > :password_authenticator" -msgid "Authenticator module" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config description at :esshd > :port" -msgid "Port to connect" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config description at :esshd > :priv_dir" -msgid "Dir with SSH keys" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 msgctxt "config description at :ex_aws-:s3 > :access_key_id" msgid "S3 access key ID" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :host" msgid "S3 host" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :region" msgid "S3 region (for AWS)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :secret_access_key" msgid "Secret access key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger > :backends" msgid "Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:console > :format" msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:console > :level" msgid "Log level" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger > :format" msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger > :ident" msgid "A string that's prepended to every message, and is typically set to the app name" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger > :level" msgid "Log level" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma > :admin_token" msgid "Admin token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :blockers_visible" msgid "Whether a user can see someone who has blocked them" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :follow_handshake_timeout" msgid "Following handshake timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :note_replies_output_limit" msgid "The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :outgoing_blocks" msgid "Whether to federate blocks to other instances" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :sign_object_fetches" msgid "Sign object fetches with HTTP signatures" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :unfollow_blocked" msgid "Whether blocks result in people getting unfollowed" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets > :default_mascot" msgid "This will be used as the default mascot on MastoFE. Default: `:pleroma_fox_tan`" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets > :default_user_avatar" msgid "URL of the default user avatar" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets > :mascots" msgid "Keyword of mascots, each element must contain both an URL and a mime_type key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :auth_template" msgid "Authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :enforce_oauth_admin_scope_usage" msgid "OAuth admin scope requirement toggle. If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token (client app must support admin scopes). If disabled and token doesn't have admin scope(s), `is_admin` user flag grants access to admin-specific actions." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :oauth_consumer_strategies" msgid "The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable. Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\" (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :oauth_consumer_template" msgid "OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool > :connect_timeout" msgid "Timeout while `gun` will wait until connection is up. Default: 5000ms." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool > :connection_acquisition_retries" msgid "Number of attempts to acquire the connection from the pool if it is overloaded. Default: 5" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool > :connection_acquisition_wait" msgid "Timeout to acquire a connection from pool. The total max time is this value multiplied by the number of retries. Default: 250ms." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool > :max_connections" msgid "Maximum number of connections in the pool. Default: 250 connections." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool > :reclaim_multiplier" msgid "Multiplier for the number of idle connection to be reclaimed if the pool is full. For example if the pool maxes out at 250 connections and this setting is set to 0.3, the pool will reclaim at most 75 idle connections if it's overloaded. Default: 0.1" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest" msgid "emails of \"what you've missed\" for users who have been inactive for a while" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :active" msgid "Globally enable or disable digest emails" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :inactivity_threshold" msgid "Minimum user inactivity threshold" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :interval" msgid "Minimum interval between digest emails to one user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :schedule" msgid "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\"." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :default_manifest" msgid "Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :groups" msgid "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." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :pack_extensions" msgid "A list of file extensions for emojis, when no emoji.txt for a pack is present" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :shortcode_globs" msgid "Location of custom emoji files. * can be used as a wildcard." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:features > :improved_hashtag_timeline" msgid "Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed > :post_title" msgid "Configure title rendering" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed > :post_title > :max_length" msgid "Maximum number of characters before truncating title" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed > :post_title > :omission" msgid "Replacement which will be used after truncating string" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe" msgid "Settings for Pleroma FE" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :alwaysShowSubjectInput" msgid "When disabled, auto-hide the subject field if it's empty" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :background" msgid "URL of the background, unless viewing a user profile with a background that is set" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :collapseMessageWithSubject" msgid "When a message has a subject (aka Content Warning), collapse it by default" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :greentext" msgid "Enables green text on lines prefixed with the > character" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideFilteredStatuses" msgid "Hides filtered statuses from timelines" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideMutedPosts" msgid "Hides muted statuses from timelines" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hidePostStats" msgid "Hide notices statistics (repeats, favorites, ...)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideSitename" msgid "Hides instance name from PleromaFE banner" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :hideUserStats" msgid "Hide profile statistics (posts, posts per day, followers, followings, ...)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :logo" msgid "URL of the logo, defaults to Pleroma's logo" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :logoMargin" msgid "Allows you to adjust vertical margins between logo boundary and navbar borders. The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :logoMask" msgid "By default it assumes logo used will be monochrome with alpha channel to be compatible with both light and dark themes. If you want a colorful logo you must disable logoMask." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :minimalScopesMode" msgid "Limit scope selection to Direct, User default, and Scope of post replying to. Also prevents replying to a DM with a public post from PleromaFE." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :nsfwCensorImage" msgid "URL of the image to use for hiding NSFW media attachments in the timeline" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :postContentType" msgid "Default post formatting option" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootLogin" msgid "Relative URL which indicates where to redirect when a user is logged in" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootNoLogin" msgid "Relative URL which indicates where to redirect when a user isn't logged in" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :scopeCopy" msgid "Copy the scope (private/unlisted/public) in replies to posts by default" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :showFeaturesPanel" msgid "Enables panel displaying functionality of the instance on the About page" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :showInstanceSpecificPanel" msgid "Whether to show the instance's custom panel" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :sidebarRight" msgid "Change alignment of sidebar and panels to the right" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :subjectLineBehavior" msgid "Allows changing the default behaviour of subject lines in replies.\n `email`: copy and preprend re:, as in email,\n `masto`: copy verbatim, as in Mastodon,\n `noop`: don't copy the subject." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe > :theme" msgid "Which theme to use. Available themes are defined in styles.json" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :admin" msgid "Admin frontend" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :admin > name" msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :admin > ref" msgid "Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available" msgid "A map containing available frontends and parameters for their installation." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > build_dir" msgid "The directory inside the zip file " msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > build_url" msgid "Either an url to a zip file containing the frontend or a template to build it by inserting the `ref`. The string `${ref}` will be replaced by the configured `ref`." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > custom-http-headers" msgid "The custom HTTP headers for the frontend" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > git" msgid "URL of the git repository of the frontend" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > name" msgid "Name of the frontend." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > ref" msgid "Reference of the frontend to be used." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :primary" msgid "Primary frontend, the one that is served for all pages by default" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :primary > name" msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :primary > ref" msgid "Reference of the installed frontend to be used. Valid config must include both `Name` and `Reference` values." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:gopher > :dstport" msgid "Port advertised in URLs (optional, defaults to port)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:gopher > :enabled" msgid "Enables the gopher interface" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:gopher > :ip" msgid "IP address to bind to" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:gopher > :port" msgid "Port to bind to" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :federation" msgid "Settings for federation pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :federation > :max_connections" msgid "Number workers in the pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :federation > :timeout" msgid "Timeout while `hackney` will wait for response." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :media" msgid "Settings for media pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :media > :max_connections" msgid "Number workers in the pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :media > :timeout" msgid "Timeout while `hackney` will wait for response." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :upload" msgid "Settings for upload pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :upload > :max_connections" msgid "Number workers in the pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :upload > :timeout" msgid "Timeout while `hackney` will wait for response." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http > :adapter" msgid "Adapter specific options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http > :adapter > :ssl_options" msgid "SSL options for HTTP adapter" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http > :adapter > :ssl_options > :versions" msgid "List of TLS version to use" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http > :proxy_url" msgid "Proxy URL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http > :user_agent" msgid "What user agent to use. Must be a string or an atom `:default`. Default value is `:default`." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security > :ct_max_age" msgid "The maximum age for the Expect-CT header if sent" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security > :enabled" msgid "Whether the managed content security policy is enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security > :referrer_policy" msgid "The referrer policy to use, either \"same-origin\" or \"no-referrer\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security > :report_uri" msgid "Adds the specified URL to report-uri and report-to group in CSP header" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security > :sts" msgid "Whether to additionally send a Strict-Transport-Security header" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security > :sts_max_age" msgid "The maximum age for the Strict-Transport-Security header if sent" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :account_activation_required" msgid "Require users to confirm their emails before signing in" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :account_approval_required" msgid "Require users to be manually approved by an admin before signing in" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :account_field_name_length" msgid "An account field name maximum length. Default: 512." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :account_field_value_length" msgid "An account field value maximum length. Default: 2048." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :allow_relay" msgid "Permits remote instances to subscribe to all public posts of your instance. (Important!) This may increase the visibility of your instance." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :allowed_post_formats" msgid "MIME-type list of formats allowed to be posted (transformed into HTML)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :attachment_links" msgid "Enable to automatically add attachment link text to statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :autofollowed_nicknames" msgid "Set to nicknames of (local) users that every new user should automatically follow" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :autofollowing_nicknames" msgid "Set to nicknames of (local) users that automatically follows every newly registered user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :avatar_upload_limit" msgid "File size limit of user's profile avatars" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :background_upload_limit" msgid "File size limit of user's profile backgrounds" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :banner_upload_limit" msgid "File size limit of user's profile banners" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :birthday_required" msgid "Require users to enter their birthday." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :cleanup_attachments" msgid "Enable to remove associated attachments when status is removed.\nThis will not affect duplicates and attachments without status.\nEnabling this will increase load to database when deleting statuses on larger instances.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :description" msgid "The instance's description. It can be seen in nodeinfo and `/api/v1/instance`" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :email" msgid "Email used to reach an Administrator/Moderator of the instance" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :extended_nickname_format" msgid "Enable to use extended local nicknames format (allows underscores/dashes). This will break federation with older software for theses nicknames." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :external_user_synchronization" msgid "Enabling following/followers counters synchronization for external users" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :federating" msgid "Enable federation with other instances" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :federation_incoming_replies_max_depth" msgid "Max. depth of reply-to and reply activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :federation_reachability_timeout_days" msgid "Timeout (in days) of each external federation target being unreachable prior to pausing federating to it" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :healthcheck" msgid "If enabled, system data will be shown on `/api/pleroma/healthcheck`" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :instance_thumbnail" msgid "The instance thumbnail can be any image that represents your instance and is used by some apps or services when they display information about your instance." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :invites_enabled" msgid "Enable user invitations for admins (depends on `registrations_open` being disabled)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :limit" msgid "Posts character limit (CW/Subject included in the counter)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :limit_to_local_content" msgid "Limit unauthenticated users to search for local statutes and users only. Default: `:unauthenticated`." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :max_account_fields" msgid "The maximum number of custom fields in the user profile. Default: 10." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :max_endorsed_users" msgid "The maximum number of recommended accounts. 0 will disable the feature." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :max_media_attachments" msgid "Maximum number of post media attachments" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :max_pinned_statuses" msgid "The maximum number of pinned statuses. 0 will disable the feature." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :max_remote_account_fields" msgid "The maximum number of custom fields in the remote user profile. Default: 20." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :max_report_comment_size" msgid "The maximum size of the report comment. Default: 1000." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication" msgid "Multi-factor authentication settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes" msgid "MFA backup codes settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :length" msgid "Determines the length of backup one-time pass-codes, in characters. Defaults to 16 characters." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :number" msgid "Number of backup codes to generate." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp" msgid "TOTP settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp > :digits" msgid "Determines the length of a one-time pass-code, in characters. Defaults to 6 characters." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp > :period" msgid "A period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :name" msgid "Name of the instance" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :notify_email" msgid "Envelope FROM address for mail sent via Pleroma" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :poll_limits" msgid "A map with poll limits for local polls" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :poll_limits > :max_expiration" msgid "Maximum expiration time (in seconds)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :poll_limits > :max_option_chars" msgid "Maximum number of characters per option" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :poll_limits > :max_options" msgid "Maximum number of options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :poll_limits > :min_expiration" msgid "Minimum expiration time (in seconds)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :profile_directory" msgid "Enable profile directory." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :public" msgid "Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. Note: when setting to `false`, please also check `:restrict_unauthenticated` setting." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :quarantined_instances" msgid "List of ActivityPub instances where private (DMs, followers-only) activities will not be sent and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :registration_reason_length" msgid "Maximum registration reason length. Default: 500." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :registrations_open" msgid "Enable registrations for anyone. Invitations require this setting to be disabled." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :remote_limit" msgid "Hard character limit beyond which remote posts will be dropped" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :remote_post_retention_days" msgid "The default amount of days to retain remote posts when pruning the database" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :safe_dm_mentions" msgid "If enabled, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\"). Default: disabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :show_reactions" msgid "Let favourites and emoji reactions be viewed through the API." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :skip_thread_containment" msgid "Skip filtering out broken threads. Default: enabled." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :static_dir" msgid "Instance static directory" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :upload_limit" msgid "File size limit of uploads (except for avatar, background, banner)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :user_bio_length" msgid "A user bio maximum length. Default: 5000." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :user_name_length" msgid "A user name maximum length. Default: 100." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instances_favicons > :enabled" msgid "Allow/disallow displaying and getting instances favicons" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :base" msgid "LDAP base, e.g. \"dc=example,dc=com\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :enabled" msgid "Enables LDAP authentication" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :host" msgid "LDAP server hostname" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :port" msgid "LDAP port, e.g. 389 or 636" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :ssl" msgid "Enable to use SSL, usually implies the port 636" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :sslopts" msgid "Additional SSL options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :sslopts > :cacertfile" msgid "Path to file with PEM encoded cacerts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :sslopts > :verify" msgid "Type of cert verification" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :tls" msgid "Enable to use STARTTLS, usually implies the port 389" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :tlsopts" msgid "Additional TLS options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :tlsopts > :cacertfile" msgid "Path to file with PEM encoded cacerts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :tlsopts > :verify" msgid "Type of cert verification" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap > :uid" msgid "LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:majic_pool > :size" msgid "Number of majic workers to start." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:manifest > :background_color" msgid "Describe the background color of the app" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:manifest > :icons" msgid "Describe the icons of the app" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:manifest > :theme_color" msgid "Describe the theme color of the app" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:markup > :scrub_policy" msgid "Module names are shortened (removed leading `Pleroma.HTML.` part), but on adding custom module you need to use full name." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_preview_proxy > :enabled" msgid "Enables proxying of remote media preview to the instance's proxy. Requires enabled media proxy." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_preview_proxy > :image_quality" msgid "Quality of the output. Ranges from 0 (min quality) to 100 (max quality)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_preview_proxy > :min_content_length" msgid "Min content length (in bytes) to perform preview. Media smaller in size will be served without thumbnailing." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_preview_proxy > :thumbnail_max_height" msgid "Max height of preview thumbnail for images (video preview always has original dimensions)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_preview_proxy > :thumbnail_max_width" msgid "Max width of preview thumbnail for images (video preview always has original dimensions)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :base_url" msgid "The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :enabled" msgid "Enables proxying of remote media via the instance's proxy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :invalidation > :enabled" msgid "Enables media cache object invalidation." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :invalidation > :provider" msgid "Module which will be used to purge objects from the cache." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :proxy_opts" msgid "Internal Pleroma.ReverseProxy settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :proxy_opts > :max_body_length" msgid "Maximum file size (in bytes) allowed through the Pleroma MediaProxy cache." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :proxy_opts > :max_read_duration" msgid "Timeout (in milliseconds) of GET request to the remote URI." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :proxy_opts > :redirect_on_failure" msgid "Redirects the client to the origin server upon encountering HTTP errors.\n\nNote that files larger than Max Body Length will trigger an error. (e.g., Peertube videos)\n\n\n**WARNING:** This setting will allow larger files to be accessed, but exposes the\n\nIP addresses of your users to the other servers, bypassing the MediaProxy.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy > :whitelist" msgid "List of hosts with scheme to bypass the MediaProxy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:modules > :runtime_dir" msgid "A path to custom Elixir modules (such as MRF policies)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf > :policies" msgid "A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf > :transparency" msgid "Make the content of your Message Rewrite Facility settings public (via nodeinfo)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf > :transparency_exclusions" msgid "Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_activity_expiration > :days" msgid "Default global expiration time for all local activities (in days)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_follow_bot > :follower_nickname" msgid "The name of the bot account to use for following newly discovered users." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hashtag > :federated_timeline_removal" msgid "A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hashtag > :reject" msgid "A list of hashtags which result in message being rejected." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hashtag > :sensitive" msgid "A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hellthread > :delist_threshold" msgid "Number of mentioned users after which the message gets removed from timelines anddisables notifications. Set to 0 to disable." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hellthread > :reject_threshold" msgid "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_keyword > :federated_timeline_removal" msgid " A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_keyword > :reject" msgid " A list of patterns which result in message being rejected.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_keyword > :replace" msgid " **Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n\n **Replacement**: a string. Leaving the field empty is permitted.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_mention > :actors" msgid "A list of actors for which any post mentioning them will be dropped" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_object_age > :threshold" msgid "Required age (in seconds) of a post before actions are taken." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_rejectnonpublic > :allow_direct" msgid "Whether to allow direct messages" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_rejectnonpublic > :allow_followersonly" msgid "Whether to allow followers-only posts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :accept" msgid "List of instances to only accept activities from (except deletes) and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :avatar_removal" msgid "List of instances to strip avatars from and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :banner_removal" msgid "List of instances to strip banners from and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :federated_timeline_removal" msgid "List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :followers_only" msgid "Force posts from the given instances to be visible by followers only and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :media_nsfw" msgid "List of instances to tag all media as NSFW (sensitive) from and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :media_removal" msgid "List of instances to strip media attachments from and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :reject" msgid "List of instances to reject activities from (except deletes) and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :reject_deletes" msgid "List of instances to reject deletions from and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple > :report_removal" msgid "List of instances to reject reports from and the reason for doing so" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_steal_emoji > :hosts" msgid "List of hosts to steal emojis from" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_steal_emoji > :rejected_shortcodes" msgid " A list of patterns or matches to reject shortcodes with.\n\n Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_steal_emoji > :size_limit" msgid "File size limit (in bytes), checked before an emoji is saved to the disk" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_subchain > :match_actor" msgid "Matches a series of regular expressions against the actor field" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_vocabulary > :accept" msgid "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_vocabulary > :reject" msgid "A list of ActivityStreams terms to reject. If empty, no messages are rejected." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:oauth2 > :clean_expired_tokens" msgid "Enable a background job to clean expired OAuth tokens. Default: disabled." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:oauth2 > :issue_new_refresh_token" msgid "Keeps old refresh token or generate new refresh token when to obtain an access token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:oauth2 > :token_expires_in" msgid "The lifetime in seconds of the access token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :default" msgid "Settings for default pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :default > :max_waiting" msgid "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :default > :recv_timeout" msgid "Timeout for the pool while gun will wait for response" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :default > :size" msgid "Maximum number of concurrent requests in the pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :federation" msgid "Settings for federation pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :federation > :max_waiting" msgid "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :federation > :recv_timeout" msgid "Timeout for the pool while gun will wait for response" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :federation > :size" msgid "Maximum number of concurrent requests in the pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :media" msgid "Settings for media pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :media > :max_waiting" msgid "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :media > :recv_timeout" msgid "Timeout for the pool while gun will wait for response" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :media > :size" msgid "Maximum number of concurrent requests in the pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :upload" msgid "Settings for upload pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :upload > :max_waiting" msgid "Maximum number of requests waiting for other requests to finish. After this number is reached, the pool will start returning errrors when a new request is made" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :upload > :recv_timeout" msgid "Timeout for the pool while gun will wait for response" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools > :upload > :size" msgid "Maximum number of concurrent requests in the pool." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:populate_hashtags_table > :fault_rate_allowance" msgid "Max accepted rate of objects that failed in the migration. Any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if hashtags transfer failed for all records." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:populate_hashtags_table > :sleep_interval_ms" msgid "Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :app_account_creation" msgid "For registering user accounts from the same IP address" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :authentication" msgid "For authentication create / password check / user existence check requests" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :relation_id_action" msgid "For actions on relation with a specific user (follow, unfollow)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :relations_actions" msgid "For actions on relationships with all users (follow, unfollow)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :search" msgid "For the search requests (account & status search etc.)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :status_id_action" msgid "For fav / unfav or reblog / unreblog actions on the same status by the same user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :statuses_actions" msgid "For create / delete / fav / unfav / reblog / unreblog actions on any statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit > :timeline" msgid "For requests to timelines (each timeline has it's own limiter)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :activities" msgid "Settings for statuses." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :activities > :local" msgid "Disallow view local statuses." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :activities > :remote" msgid "Disallow view remote statuses." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :profiles" msgid "Settings for user profiles." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :profiles > :local" msgid "Disallow view local user profiles." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :profiles > :remote" msgid "Disallow view remote user profiles." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines" msgid "Settings for public and federated timelines." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines > :federated" msgid "Disallow view federated timeline." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:restrict_unauthenticated > :timelines > :local" msgid "Disallow view public timeline." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media > :enabled" msgid "Enables RichMedia parsing of URLs" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media > :failure_backoff" msgid "Amount of milliseconds after request failure, during which the request will not be retried." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media > :ignore_hosts" msgid "List of hosts which will be ignored by the metadata parser" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media > :ignore_tld" msgid "List TLDs (top-level domains) which will ignore for parse metadata" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media > :parsers" msgid "List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom module you need to use full name." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media > :ttl_setters" msgid "List of rich media TTL setters. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parser.` part), but on adding custom module you need to use full name." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:shout > :enabled" msgid "Enables the backend Shoutbox chat feature." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:shout > :limit" msgid "Shout message character limit." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:static_fe > :enabled" msgid "Enables the rendering of static HTML. Default: disabled." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:streamer > :overflow_workers" msgid "Maximum number of workers created if pool is empty" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:streamer > :workers" msgid "Number of workers to send notifications" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:uri_schemes > :valid_schemes" msgid "List of the scheme part that is considered valid to be an URL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:web_cache_ttl > :activity_pub" msgid "Activity pub routes (except question activities). Default: `nil` (no expiration)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:web_cache_ttl > :activity_pub_question" msgid "Activity pub routes (question activities). Default: `30_000` (30 seconds)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :chat_message > :enabled" msgid "Enables sending a chat message to newly registered users" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :chat_message > :message" msgid "A message that will be sent to newly registered users as a chat message" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :chat_message > :sender_nickname" msgid "The nickname of the local user that sends a welcome chat message" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :direct_message > :enabled" msgid "Enables sending a direct message to newly registered users" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :direct_message > :message" msgid "A message that will be sent to newly registered users" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :direct_message > :sender_nickname" msgid "The nickname of the local user that sends a welcome message" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :email > :enabled" msgid "Enables sending an email to newly registered users" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :email > :html" msgid "HTML content of the welcome email. EEX template with user and instance_name variables can be used." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :email > :sender" msgid "Email address and/or nickname that will be used to send the welcome email." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :email > :subject" msgid "Subject of the welcome email. EEX template with user and instance_name variables can be used." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome > :email > :text" msgid "Text content of the welcome email. EEX template with user and instance_name variables can be used." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:workers > :retries" msgid "Max retry attempts for failed jobs, per `Oban` queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy" msgid "Concurrent limits configuration for MediaProxyWarmingPolicy." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_running" msgid "Max running concurrently jobs." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_waiting" msgid "Max waiting jobs." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers" msgid "Concurrent limits configuration for getting RichMedia for activities." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_running" msgid "Max running concurrently jobs." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_waiting" msgid "Max waiting jobs." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :crontab" msgid "Settings for cron background jobs" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :log" msgid "Logs verbose mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues" msgid "Background jobs queues (keys: queues, values: max numbers of concurrent jobs)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :activity_expiration" msgid "Activity expiration queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :attachments_cleanup" msgid "Attachment deletion queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :background" msgid "Background queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :backup" msgid "Backup queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :federator_incoming" msgid "Incoming federation queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :federator_outgoing" msgid "Outgoing federation queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :mailer" msgid "Email sender queue, see Pleroma.Emails.Mailer" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :scheduled_activities" msgid "Scheduled activities queue, see Pleroma.ScheduledActivities" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :transmogrifier" msgid "Transmogrifier queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban > :queues > :web_push" msgid "Web push notifications queue" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha > :enabled" msgid "Whether the captcha should be shown on registration" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha > :method" msgid "The method/service to use for captcha" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha > :seconds_valid" msgid "The time in seconds for which the captcha is valid" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha > :endpoint" msgid "The kocaptcha endpoint to use" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > :adapter" msgid "One of the mail adapters listed in [Swoosh documentation](https://hexdocs.pm/swoosh/Swoosh.html#module-adapters)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:auth" msgid "SMTP AUTH enforcement mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:password" msgid "SMTP AUTH password" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:port" msgid "SMTP port" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:relay" msgid "Hostname or IP address" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:retries" msgid "SMTP temporary (4xx) error retries" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:ssl" msgid "Use Implicit SSL/TLS. e.g. port 465" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:tls" msgid "Explicit TLS (STARTTLS) enforcement mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:username" msgid "SMTP AUTH username" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.NewUsersDigestEmail > :enabled" msgid "Enables new users admin digest email when `true`" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail > :logo" msgid "A path to a custom logo. Set it to `nil` to use the default Pleroma logo." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail > :styling" msgid "A map with color settings for email templates." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter > :class" msgid "Specify the class to be added to the generated link. Disable to clear." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter > :extra" msgid "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter > :new_window" msgid "Link URLs will open in a new window/tab." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter > :rel" msgid "Override the rel attribute. Disable to clear." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter > :strip_prefix" msgid "Strip the scheme prefix." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter > :truncate" msgid "Set to a number to truncate URLs longer than the number. Truncated URLs will end in `...`" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter > :validate_tld" msgid "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)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.ScheduledActivity > :daily_user_limit" msgid "The number of scheduled activities a user is allowed to create in a single day. Default: 25." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.ScheduledActivity > :enabled" msgid "Whether scheduled activities are sent to the job queue to be executed" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.ScheduledActivity > :total_user_limit" msgid "The number of scheduled activities a user is allowed to create in total. Default: 300." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload > :base_url" msgid "Base URL for the uploads. Required if you use a CDN or host attachments under a different domain." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload > :filename_display_max_length" msgid "Set max length of a filename to display. 0 = no limit. Default: 30" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload > :filters" msgid "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload > :link_name" msgid "If enabled, a name parameter will be added to the URL of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload > :proxy_remote" msgid "Proxy requests to the remote uploader.\n\nUseful if media upload endpoint is not internet accessible.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload > :uploader" msgid "Module which will be used for uploads" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename > :text" msgid "Text to replace filenames in links. If no setting, {random}.extension will be used. You can get the original filename extension by using {extension}, for example custom-file-name.{extension}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify > :args" msgid "List of actions for the mogrify command. It's possible to add self-written settings as string. For example `auto-orient, strip, {\"resize\", \"3840x1080>\"}` value will be parsed into valid list of the settings." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.Local > :uploads" msgid "Path where user's uploads will be saved" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :bucket" msgid "S3 bucket" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :bucket_namespace" msgid "S3 bucket namespace" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :streaming_enabled" msgid "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." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.S3 > :truncated_namespace" msgid "If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or \"\" etc. For example, when using CDN to S3 virtual host format, set \"\". At this time, write CNAME to CDN in Upload base_url." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.User > :email_blacklist" msgid "List of email domains users may not register with." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.User > :restricted_nicknames" msgid "List of nicknames users may not register with." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.User.Backup > :limit_days" msgid "Limit user to export not more often than once per N days" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.User.Backup > :purge_after_days" msgid "Remove backup achives after N days" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate > :strict" msgid "Enables strict input validation (useful in development, not recommended in production)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :headers" msgid "HTTP headers of request" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :method" msgid "HTTP method of request. Default: :purge" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options" msgid "Request options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :script_path" msgid "Path to executable script which will purge cached items." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :url_format" msgid "Optional URL format preprocessing. Only required for Apache's htcacheclean." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Metadata > :providers" msgid "List of metadata providers to enable" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Metadata > :unfurl_nsfw" msgid "When enabled NSFW attachments will be shown in previews" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :enabled" msgid "Enable/disable the plug. Default: disabled." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :headers" msgid " A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `[\"x-forwarded-for\"]`.\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :proxies" msgid "A list of upstream proxy IP subnets in CIDR notation from which we will parse the content of `headers`. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp > :reserved" msgid " A list of reserved IP subnets in CIDR notation which should be ignored if found in `headers`. Defaults to `[\"127.0.0.0/8\", \"::1/128\", \"fc00::/7\", \"10.0.0.0/8\", \"172.16.0.0/12\", \"192.168.0.0/16\"]`\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Preload > :providers" msgid "List of preload providers to enable" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :enabled" msgid "Enables expired activities addition & deletion" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :min_lifetime" msgid "Minimum lifetime for ephemeral activity (in seconds)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :auth" msgid "Enables HTTP Basic Auth for app metrics endpoint." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :enabled" msgid "[Pleroma extension] Enables app metrics endpoint." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :format" msgid "App metrics endpoint output format." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :ip_whitelist" msgid "Restrict access of app metrics endpoint to the specified IP addresses." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :path" msgid "App metrics endpoint URI path." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :web_push_encryption-:vapid_details > :private_key" msgid "VAPID private key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :web_push_encryption-:vapid_details > :public_key" msgid "VAPID public key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :web_push_encryption-:vapid_details > :subject" msgid "A mailto link for the administrative contact. It's best if this email is not a personal email address, but rather a group email to the instance moderation team." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :cors_plug > :credentials" msgid "Credentials" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :cors_plug > :expose" msgid "Expose" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :cors_plug > :headers" msgid "Headers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :cors_plug > :max_age" msgid "Max age" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :cors_plug > :methods" msgid "Methods" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 -msgctxt "config label at :esshd > :enabled" -msgid "Enabled" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config label at :esshd > :handler" -msgid "Handler" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config label at :esshd > :password_authenticator" -msgid "Password authenticator" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config label at :esshd > :port" -msgid "Port" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 -msgctxt "config label at :esshd > :priv_dir" -msgid "Priv dir" -msgstr "" - -#, elixir-autogen, elixir-format -#: lib/pleroma/docs/translator.ex:5 msgctxt "config label at :ex_aws-:s3 > :access_key_id" msgid "Access key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :ex_aws-:s3 > :host" msgid "Host" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :ex_aws-:s3 > :region" msgid "Region" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :ex_aws-:s3 > :secret_access_key" msgid "Secret access key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger > :backends" msgid "Backends" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:console > :format" msgid "Format" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:console > :level" msgid "Level" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:console > :metadata" msgid "Metadata" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:ex_syslogger > :format" msgid "Format" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:ex_syslogger > :ident" msgid "Ident" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:ex_syslogger > :level" msgid "Level" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:ex_syslogger > :metadata" msgid "Metadata" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :mime > :types" msgid "Types" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :mime > :types > application/activity+json" msgid "\"application/activity+json\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :mime > :types > application/jrd+json" msgid "\"application/jrd+json\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :mime > :types > application/ld+json" msgid "\"application/ld+json\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :mime > :types > application/xml" msgid "\"application/xml\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :mime > :types > application/xrd+xml" msgid "\"application/xrd+xml\"" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma > :admin_token" msgid "Admin token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma > Pleroma.Web.Auth.Authenticator" msgid "Pleroma.Web.Auth.Authenticator" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub > :blockers_visible" msgid "Blockers visible" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub > :follow_handshake_timeout" msgid "Follow handshake timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub > :note_replies_output_limit" msgid "Note replies output limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub > :outgoing_blocks" msgid "Outgoing blocks" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub > :sign_object_fetches" msgid "Sign object fetches" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub > :unfollow_blocked" msgid "Unfollow blocked" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:assets > :default_mascot" msgid "Default mascot" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:assets > :default_user_avatar" msgid "Default user avatar" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:assets > :mascots" msgid "Mascots" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:auth > :auth_template" msgid "Auth template" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:auth > :enforce_oauth_admin_scope_usage" msgid "Enforce OAuth admin scope usage" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:auth > :oauth_consumer_strategies" msgid "OAuth consumer strategies" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:auth > :oauth_consumer_template" msgid "OAuth consumer template" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:connections_pool > :connect_timeout" msgid "Connect timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:connections_pool > :connection_acquisition_retries" msgid "Connection acquisition retries" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:connections_pool > :connection_acquisition_wait" msgid "Connection acquisition wait" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:connections_pool > :max_connections" msgid "Max connections" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:connections_pool > :reclaim_multiplier" msgid "Reclaim multiplier" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications > :digest" msgid "Digest" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications > :digest > :active" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications > :digest > :inactivity_threshold" msgid "Inactivity threshold" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications > :digest > :interval" msgid "Interval" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications > :digest > :schedule" msgid "Schedule" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji > :default_manifest" msgid "Default manifest" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji > :groups" msgid "Groups" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji > :pack_extensions" msgid "Pack extensions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji > :shared_pack_cache_seconds_per_file" msgid "Shared pack cache s/file" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji > :shortcode_globs" msgid "Shortcode globs" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:features > :improved_hashtag_timeline" msgid "Improved hashtag timeline" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:feed > :post_title" msgid "Post title" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:feed > :post_title > :max_length" msgid "Max length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:feed > :post_title > :omission" msgid "Omission" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe" msgid "Pleroma FE" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :alwaysShowSubjectInput" msgid "Always show subject input" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :background" msgid "Background" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :collapseMessageWithSubject" msgid "Collapse message with subject" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :greentext" msgid "Greentext" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideFilteredStatuses" msgid "Hide Filtered Statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideMutedPosts" msgid "Hide Muted Posts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hidePostStats" msgid "Hide post stats" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideSitename" msgid "Hide Sitename" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :hideUserStats" msgid "Hide user stats" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logo" msgid "Logo" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logoMargin" msgid "Logo margin" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :logoMask" msgid "Logo mask" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :minimalScopesMode" msgid "Minimal scopes mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :nsfwCensorImage" msgid "NSFW Censor Image" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :postContentType" msgid "Post Content Type" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootLogin" msgid "Redirect root login" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :redirectRootNoLogin" msgid "Redirect root no login" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :scopeCopy" msgid "Scope copy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :showFeaturesPanel" msgid "Show instance features panel" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :showInstanceSpecificPanel" msgid "Show instance specific panel" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :sidebarRight" msgid "Sidebar on Right" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :subjectLineBehavior" msgid "Subject line behavior" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations > :pleroma_fe > :theme" msgid "Theme" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :admin" msgid "Admin" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :admin > name" msgid "Name" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :admin > ref" msgid "Reference" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :available" msgid "Available" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :available > build_dir" msgid "Build directory" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :available > build_url" msgid "Build URL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :available > custom-http-headers" msgid "Custom HTTP headers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :available > git" msgid "Git Repository URL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :available > name" msgid "Name" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :available > ref" msgid "Reference" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :primary" msgid "Primary" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :primary > name" msgid "Name" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends > :primary > ref" msgid "Reference" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:gopher > :dstport" msgid "Dstport" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:gopher > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:gopher > :ip" msgid "IP" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:gopher > :port" msgid "Port" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :federation" msgid "Federation" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :federation > :max_connections" msgid "Max connections" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :federation > :timeout" msgid "Timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :media" msgid "Media" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :media > :max_connections" msgid "Max connections" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :media > :timeout" msgid "Timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :upload" msgid "Upload" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :upload > :max_connections" msgid "Max connections" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools > :upload > :timeout" msgid "Timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http > :adapter" msgid "Adapter" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http > :adapter > :ssl_options" msgid "SSL Options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http > :adapter > :ssl_options > :versions" msgid "Versions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http > :proxy_url" msgid "Proxy URL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http > :send_user_agent" msgid "Send user agent" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http > :user_agent" msgid "User agent" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security > :ct_max_age" msgid "CT max age" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security > :referrer_policy" msgid "Referrer policy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security > :report_uri" msgid "Report URI" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security > :sts" msgid "STS" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security > :sts_max_age" msgid "STS max age" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :account_activation_required" msgid "Account activation required" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :account_approval_required" msgid "Account approval required" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :account_field_name_length" msgid "Account field name length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :account_field_value_length" msgid "Account field value length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :allow_relay" msgid "Allow relay" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :allowed_post_formats" msgid "Allowed post formats" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :attachment_links" msgid "Attachment links" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :autofollowed_nicknames" msgid "Autofollowed nicknames" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :autofollowing_nicknames" msgid "Autofollowing nicknames" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :avatar_upload_limit" msgid "Avatar upload limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :background_upload_limit" msgid "Background upload limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :banner_upload_limit" msgid "Banner upload limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :birthday_min_age" msgid "Birthday min age" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :birthday_required" msgid "Birthday required" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :cleanup_attachments" msgid "Cleanup attachments" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :description" msgid "Description" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :email" msgid "Admin Email Address" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :extended_nickname_format" msgid "Extended nickname format" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :external_user_synchronization" msgid "External user synchronization" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :federating" msgid "Federating" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :federation_incoming_replies_max_depth" msgid "Fed. incoming replies max depth" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :federation_reachability_timeout_days" msgid "Fed. reachability timeout days" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :healthcheck" msgid "Healthcheck" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :instance_thumbnail" msgid "Instance thumbnail" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :invites_enabled" msgid "Invites enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :limit" msgid "Limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :limit_to_local_content" msgid "Limit to local content" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :max_account_fields" msgid "Max account fields" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :max_endorsed_users" msgid "Max endorsed users" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :max_media_attachments" msgid "Max media attachments" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :max_pinned_statuses" msgid "Max pinned statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :max_remote_account_fields" msgid "Max remote account fields" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :max_report_comment_size" msgid "Max report comment size" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :multi_factor_authentication" msgid "Multi factor authentication" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :backup_codes" msgid "Backup codes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :length" msgid "Length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :number" msgid "Number" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :totp" msgid "TOTP settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :totp > :digits" msgid "Digits" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :multi_factor_authentication > :totp > :period" msgid "Period" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :name" msgid "Name" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :notify_email" msgid "Sender Email Address" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :poll_limits" msgid "Poll limits" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :poll_limits > :max_expiration" msgid "Max expiration" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :poll_limits > :max_option_chars" msgid "Max option chars" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :poll_limits > :max_options" msgid "Max options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :poll_limits > :min_expiration" msgid "Min expiration" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :profile_directory" msgid "Profile directory" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :public" msgid "Public" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :quarantined_instances" msgid "Quarantined instances" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :registration_reason_length" msgid "Registration reason length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :registrations_open" msgid "Registrations open" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :remote_limit" msgid "Remote limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :remote_post_retention_days" msgid "Remote post retention days" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :safe_dm_mentions" msgid "Safe DM mentions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :show_reactions" msgid "Show reactions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :skip_thread_containment" msgid "Skip thread containment" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :static_dir" msgid "Static dir" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :upload_limit" msgid "Upload limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :user_bio_length" msgid "User bio length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :user_name_length" msgid "User name length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instances_favicons > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :base" msgid "Base" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :host" msgid "Host" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :port" msgid "Port" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :ssl" msgid "SSL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :sslopts" msgid "SSL options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :sslopts > :cacertfile" msgid "Cacertfile" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :sslopts > :verify" msgid "Verify" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :tls" msgid "TLS" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :tlsopts" msgid "TLS options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :tlsopts > :cacertfile" msgid "Cacertfile" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :tlsopts > :verify" msgid "Verify" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap > :uid" msgid "UID" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:majic_pool > :size" msgid "Size" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:manifest > :background_color" msgid "Background color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:manifest > :icons" msgid "Icons" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:manifest > :theme_color" msgid "Theme color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup > :allow_fonts" msgid "Allow fonts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup > :allow_headings" msgid "Allow headings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup > :allow_inline_images" msgid "Allow inline images" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup > :allow_tables" msgid "Allow tables" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup > :scrub_policy" msgid "Scrub policy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy > :image_quality" msgid "Image quality" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy > :min_content_length" msgid "Min content length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy > :thumbnail_max_height" msgid "Thumbnail max height" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy > :thumbnail_max_width" msgid "Thumbnail max width" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :base_url" msgid "Base URL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :invalidation" msgid "Invalidation" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :invalidation > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :invalidation > :provider" msgid "Provider" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :proxy_opts" msgid "Advanced MediaProxy Options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :proxy_opts > :max_body_length" msgid "Max body length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :proxy_opts > :max_read_duration" msgid "Max read duration" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :proxy_opts > :redirect_on_failure" msgid "Redirect on failure" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy > :whitelist" msgid "Whitelist" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:modules > :runtime_dir" msgid "Runtime dir" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf > :policies" msgid "Policies" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf > :transparency" msgid "MRF transparency" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf > :transparency_exclusions" msgid "MRF transparency exclusions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_activity_expiration > :days" msgid "Days" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_follow_bot > :follower_nickname" msgid "Follower nickname" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hashtag > :federated_timeline_removal" msgid "Federated timeline removal" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hashtag > :reject" msgid "Reject" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hashtag > :sensitive" msgid "Sensitive" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hellthread > :delist_threshold" msgid "Delist threshold" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hellthread > :reject_threshold" msgid "Reject threshold" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_keyword > :federated_timeline_removal" msgid "Federated timeline removal" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_keyword > :reject" msgid "Reject" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_keyword > :replace" msgid "Replace" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_mention > :actors" msgid "Actors" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_normalize_markup > :scrub_policy" msgid "Scrub policy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_object_age > :actions" msgid "Actions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_object_age > :threshold" msgid "Threshold" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_rejectnonpublic > :allow_direct" msgid "Allow direct" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_rejectnonpublic > :allow_followersonly" msgid "Allow followers-only" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :accept" msgid "Accept" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :avatar_removal" msgid "Avatar removal" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :banner_removal" msgid "Banner removal" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :federated_timeline_removal" msgid "Federated timeline removal" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :followers_only" msgid "Followers only" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :media_nsfw" msgid "Media NSFW" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :media_removal" msgid "Media removal" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :reject" msgid "Reject" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :reject_deletes" msgid "Reject deletes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple > :report_removal" msgid "Report removal" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_steal_emoji > :hosts" msgid "Hosts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_steal_emoji > :rejected_shortcodes" msgid "Rejected shortcodes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_steal_emoji > :size_limit" msgid "Size limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_subchain > :match_actor" msgid "Match actor" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_vocabulary > :accept" msgid "Accept" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_vocabulary > :reject" msgid "Reject" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:oauth2 > :clean_expired_tokens" msgid "Clean expired tokens" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:oauth2 > :issue_new_refresh_token" msgid "Issue new refresh token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:oauth2 > :token_expires_in" msgid "Token expires in" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :default" msgid "Default" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :default > :max_waiting" msgid "Max waiting" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :default > :recv_timeout" msgid "Recv timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :default > :size" msgid "Size" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :federation" msgid "Federation" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :federation > :max_waiting" msgid "Max waiting" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :federation > :recv_timeout" msgid "Recv timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :federation > :size" msgid "Size" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :media" msgid "Media" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :media > :max_waiting" msgid "Max waiting" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :media > :recv_timeout" msgid "Recv timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :media > :size" msgid "Size" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :upload" msgid "Upload" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :upload > :max_waiting" msgid "Max waiting" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :upload > :recv_timeout" msgid "Recv timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools > :upload > :size" msgid "Size" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:populate_hashtags_table > :fault_rate_allowance" msgid "Fault rate allowance" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:populate_hashtags_table > :sleep_interval_ms" msgid "Sleep interval ms" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :app_account_creation" msgid "App account creation" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :authentication" msgid "Authentication" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :relation_id_action" msgid "Relation ID action" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :relations_actions" msgid "Relations actions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :search" msgid "Search" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :status_id_action" msgid "Status ID action" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :statuses_actions" msgid "Statuses actions" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit > :timeline" msgid "Timeline" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :activities" msgid "Activities" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :activities > :local" msgid "Local" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :activities > :remote" msgid "Remote" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :profiles" msgid "Profiles" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :profiles > :local" msgid "Local" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :profiles > :remote" msgid "Remote" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines" msgid "Timelines" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines > :federated" msgid "Federated" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated > :timelines > :local" msgid "Local" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media > :failure_backoff" msgid "Failure backoff" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media > :ignore_hosts" msgid "Ignore hosts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media > :ignore_tld" msgid "Ignore TLD" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media > :parsers" msgid "Parsers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media > :ttl_setters" msgid "TTL setters" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:shout > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:shout > :limit" msgid "Limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:static_fe > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:streamer > :overflow_workers" msgid "Overflow workers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:streamer > :workers" msgid "Workers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:uri_schemes > :valid_schemes" msgid "Valid schemes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:user > :deny_follow_blocked" msgid "Deny follow blocked" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:web_cache_ttl > :activity_pub" msgid "Activity pub" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:web_cache_ttl > :activity_pub_question" msgid "Activity pub question" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :chat_message" msgid "Chat message" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :chat_message > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :chat_message > :message" msgid "Message" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :chat_message > :sender_nickname" msgid "Sender nickname" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :direct_message" msgid "Direct message" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :direct_message > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :direct_message > :message" msgid "Message" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :direct_message > :sender_nickname" msgid "Sender nickname" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :email" msgid "Email" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :email > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :email > :html" msgid "Html" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :email > :sender" msgid "Sender" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :email > :subject" msgid "Subject" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome > :email > :text" msgid "Text" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:workers > :retries" msgid "Retries" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy" msgid "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_running" msgid "Max running" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy > :max_waiting" msgid "Max waiting" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers" msgid "Pleroma.Web.RichMedia.Helpers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_running" msgid "Max running" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter > Pleroma.Web.RichMedia.Helpers > :max_waiting" msgid "Max waiting" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :crontab" msgid "Crontab" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :log" msgid "Log" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues" msgid "Queues" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :activity_expiration" msgid "Activity expiration" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :attachments_cleanup" msgid "Attachments cleanup" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :background" msgid "Background" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :backup" msgid "Backup" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :federator_incoming" msgid "Federator incoming" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :federator_outgoing" msgid "Federator outgoing" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :mailer" msgid "Mailer" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :scheduled_activities" msgid "Scheduled activities" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :transmogrifier" msgid "Transmogrifier" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban > :queues > :web_push" msgid "Web push" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha > :method" msgid "Method" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha > :seconds_valid" msgid "Seconds valid" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha > :endpoint" msgid "Endpoint" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > :adapter" msgid "Adapter" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > :enabled" msgid "Mailer Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:access_key" msgid "AWS Access Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:region" msgid "AWS Region" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.AmazonSES-:secret" msgid "AWS Secret Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Dyn-:api_key" msgid "Dyn API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Gmail-:access_token" msgid "GMail API Access Token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailgun-:api_key" msgid "Mailgun API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailgun-:domain" msgid "Domain" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailjet-:api_key" msgid "MailJet Public API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mailjet-:secret" msgid "MailJet Private API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Mandrill-:api_key" msgid "Mandrill API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Postmark-:api_key" msgid "Postmark API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:auth" msgid "AUTH Mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:password" msgid "Password" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:port" msgid "Port" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:relay" msgid "Relay" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:retries" msgid "Retries" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:ssl" msgid "Use SSL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:tls" msgid "STARTTLS Mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SMTP-:username" msgid "Username" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendgrid-:api_key" msgid "SendGrid API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:cmd_args" msgid "Cmd args" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:cmd_path" msgid "Cmd path" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.Sendmail-:qmail" msgid "Qmail compat mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SocketLabs-:api_key" msgid "SocketLabs API Key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SocketLabs-:server_id" msgid "Server ID" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SparkPost-:api_key" msgid "SparkPost API key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer > Swoosh.Adapters.SparkPost-:endpoint" msgid "Endpoint" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :logo" msgid "Logo" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling" msgid "Styling" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :background_color" msgid "Background color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :content_background_color" msgid "Content background color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :header_color" msgid "Header color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :link_color" msgid "Link color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :text_color" msgid "Text color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail > :styling > :text_muted_color" msgid "Text muted color" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter > :class" msgid "Class" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter > :extra" msgid "Extra" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter > :new_window" msgid "New window" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter > :rel" msgid "Rel" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter > :strip_prefix" msgid "Strip prefix" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter > :truncate" msgid "Truncate" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter > :validate_tld" msgid "Validate tld" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.ScheduledActivity > :daily_user_limit" msgid "Daily user limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.ScheduledActivity > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.ScheduledActivity > :total_user_limit" msgid "Total user limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload > :base_url" msgid "Base URL" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload > :filename_display_max_length" msgid "Filename display max length" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload > :filters" msgid "Filters" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload > :link_name" msgid "Link name" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload > :proxy_remote" msgid "Proxy remote" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload > :uploader" msgid "Uploader" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename > :text" msgid "Text" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify > :args" msgid "Args" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.Local > :uploads" msgid "Uploads" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :bucket" msgid "Bucket" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :bucket_namespace" msgid "Bucket namespace" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :streaming_enabled" msgid "Streaming enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.S3 > :truncated_namespace" msgid "Truncated namespace" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User > :email_blacklist" msgid "Email blacklist" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User > :restricted_nicknames" msgid "Restricted nicknames" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User.Backup > :limit_days" msgid "Limit days" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User.Backup > :purge_after_days" msgid "Purge after days" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate > :strict" msgid "Strict" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :headers" msgid "Headers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :method" msgid "Method" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options" msgid "Options" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http > :options > :params" msgid "Params" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :script_path" msgid "Script path" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script > :url_format" msgid "URL Format" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Metadata > :providers" msgid "Providers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Metadata > :unfurl_nsfw" msgid "Unfurl NSFW" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :headers" msgid "Headers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :proxies" msgid "Proxies" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp > :reserved" msgid "Reserved" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Preload > :providers" msgid "Providers" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity > :min_lifetime" msgid "Min lifetime" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :auth" msgid "Auth" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :enabled" msgid "Enabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :format" msgid "Format" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :ip_whitelist" msgid "IP Whitelist" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter > :path" msgid "Path" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :web_push_encryption-:vapid_details > :private_key" msgid "Private key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :web_push_encryption-:vapid_details > :public_key" msgid "Public key" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :web_push_encryption-:vapid_details > :subject" msgid "Subject" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :authorized_fetch_mode" msgid "Require HTTP signatures for AP fetches" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :short_description" msgid "Shorter version of instance description. It can be seen on `/api/v1/instance`" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub > :authorized_fetch_mode" msgid "Authorized fetch mode" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :short_description" msgid "Short description" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:delete_context_objects" msgid "`delete_context_objects` background migration settings" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:delete_context_objects > :fault_rate_allowance" msgid "Max accepted rate of objects that failed in the migration. Any value from 0.0 which tolerates no errors to 1.0 which will enable the feature even if context object deletion failed for all records." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:delete_context_objects > :sleep_interval_ms" msgid "Sleep interval between each chunk of processed records in order to decrease the load on the system (defaults to 0 and should be keep default on most instances)." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :birthday_min_age" msgid "Minimum required age (in days) for users to create account. Only used if birthday is required." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:delete_context_objects" msgid "Delete context objects" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:delete_context_objects > :fault_rate_allowance" msgid "Fault rate allowance" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:delete_context_objects > :sleep_interval_ms" msgid "Sleep interval ms" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :report_strip_status" msgid "Strip associated statuses in reports to ids when closed/resolved, otherwise keep a copy" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_object_age > :actions" msgid "A list of actions to apply to the post. `:delist` removes the post from public timelines; `:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct message; `:reject` rejects the message entirely" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :report_strip_status" msgid "Report strip status" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :admin_privileges" msgid "What extra privileges to allow admins (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :moderator_privileges" msgid "What extra privileges to allow moderators (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :admin_privileges" msgid "Admin privileges" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :moderator_privileges" msgid "Moderator privileges" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :languages" msgid "Languages to be exposed in /api/v1/instance. Should be in the format of BCP47 language codes." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/docs/translator.ex:5 +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance > :languages" msgid "Languages" msgstr "" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 10a62d2a8..4819162b7 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -10,176 +10,176 @@ msgid "" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:122 +#, elixir-autogen, elixir-format msgid "%{name} - %{count} is not a multiple of %{multiple}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:131 +#, elixir-autogen, elixir-format msgid "%{name} - %{value} is larger than exclusive maximum %{max}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:140 +#, elixir-autogen, elixir-format msgid "%{name} - %{value} is larger than inclusive maximum %{max}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:149 +#, elixir-autogen, elixir-format msgid "%{name} - %{value} is smaller than exclusive minimum %{min}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:158 +#, elixir-autogen, elixir-format msgid "%{name} - %{value} is smaller than inclusive minimum %{min}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:102 +#, elixir-autogen, elixir-format msgid "%{name} - Array items must be unique." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:114 +#, elixir-autogen, elixir-format msgid "%{name} - Array length %{length} is larger than maxItems: %{}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:106 +#, elixir-autogen, elixir-format msgid "%{name} - Array length %{length} is smaller than minItems: %{min}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:166 +#, elixir-autogen, elixir-format msgid "%{name} - Invalid %{type}. Got: %{value}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:174 +#, elixir-autogen, elixir-format msgid "%{name} - Invalid format. Expected %{format}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:51 +#, elixir-autogen, elixir-format msgid "%{name} - Invalid schema.type. Got: %{type}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:178 +#, elixir-autogen, elixir-format msgid "%{name} - Invalid value for enum." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:95 +#, elixir-autogen, elixir-format msgid "%{name} - String length is larger than maxLength: %{length}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:88 +#, elixir-autogen, elixir-format msgid "%{name} - String length is smaller than minLength: %{length}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:63 +#, elixir-autogen, elixir-format msgid "%{name} - null value where %{type} expected." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:60 +#, elixir-autogen, elixir-format msgid "%{name} - null value." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:182 +#, elixir-autogen, elixir-format msgid "Failed to cast to any schema in %{polymorphic_type}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:71 +#, elixir-autogen, elixir-format msgid "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:84 +#, elixir-autogen, elixir-format msgid "Failed to cast value to one of: %{failed_schemas}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:78 +#, elixir-autogen, elixir-format msgid "Failed to cast value using any of: %{failed_schemas}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:212 +#, elixir-autogen, elixir-format msgid "Invalid value for header: %{name}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:204 +#, elixir-autogen, elixir-format msgid "Missing field: %{name}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:208 +#, elixir-autogen, elixir-format msgid "Missing header: %{name}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:196 +#, elixir-autogen, elixir-format msgid "No value provided for required discriminator `%{field}`." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:216 +#, elixir-autogen, elixir-format msgid "Object property count %{property_count} is greater than maxProperties: %{max_properties}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:224 +#, elixir-autogen, elixir-format msgid "Object property count %{property_count} is less than minProperties: %{min_properties}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/static_fe/static_fe/error.html.eex:2 +#, elixir-autogen, elixir-format msgid "Oops" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:188 +#, elixir-autogen, elixir-format msgid "Unexpected field: %{name}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:200 +#, elixir-autogen, elixir-format msgid "Unknown schema: %{name}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/render_error.ex:192 +#, elixir-autogen, elixir-format msgid "Value used as discriminator for `%{field}` matches no schemas." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/embed/show.html.eex:43 #: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:37 +#, elixir-autogen, elixir-format msgid "announces" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/embed/show.html.eex:44 #: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:38 +#, elixir-autogen, elixir-format msgid "likes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/embed/show.html.eex:42 #: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:36 +#, elixir-autogen, elixir-format msgid "replies" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/embed/show.html.eex:27 #: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:22 +#, elixir-autogen, elixir-format msgid "sensitive media" msgstr "" diff --git a/priv/gettext/errors.pot b/priv/gettext/errors.pot index fa61d509e..d320ee1bd 100644 --- a/priv/gettext/errors.pot +++ b/priv/gettext/errors.pot @@ -89,153 +89,152 @@ msgstr "" msgid "must be equal to %{number}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:574 +#, elixir-autogen, elixir-format msgid "Account not found" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:332 +#, elixir-autogen, elixir-format msgid "Already voted" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:402 +#, elixir-autogen, elixir-format msgid "Bad request" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/controller_helper.ex:97 #: lib/pleroma/web/controller_helper.ex:103 +#, elixir-autogen, elixir-format msgid "Can't display this activity" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:334 +#, elixir-autogen, elixir-format msgid "Can't find user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:80 +#, elixir-autogen, elixir-format msgid "Can't get favorites" msgstr "" +#: lib/pleroma/web/common_api/utils.ex:464 #, elixir-autogen, elixir-format -#: lib/pleroma/web/common_api/utils.ex:457 msgid "Cannot post an empty status without attachments" msgstr "" +#: lib/pleroma/web/common_api/utils.ex:452 #, elixir-autogen, elixir-format -#: lib/pleroma/web/common_api/utils.ex:445 msgid "Comment must be up to %{max_size} characters" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/config_db.ex:199 +#, elixir-autogen, elixir-format msgid "Config with params %{params} not found" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:183 #: lib/pleroma/web/common_api.ex:187 +#, elixir-autogen, elixir-format msgid "Could not delete" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:233 +#, elixir-autogen, elixir-format msgid "Could not favorite" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:270 +#, elixir-autogen, elixir-format msgid "Could not unfavorite" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:218 +#, elixir-autogen, elixir-format msgid "Could not unrepeat" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:581 #: lib/pleroma/web/common_api.ex:590 +#, elixir-autogen, elixir-format msgid "Could not update state" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 +#, elixir-autogen, elixir-format msgid "Error." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/twitter_api.ex:105 +#, elixir-autogen, elixir-format msgid "Invalid CAPTCHA" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:146 #: lib/pleroma/web/o_auth/o_auth_controller.ex:631 +#, elixir-autogen, elixir-format msgid "Invalid credentials" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:42 +#, elixir-autogen, elixir-format msgid "Invalid credentials." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:353 +#, elixir-autogen, elixir-format msgid "Invalid indices" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 +#, elixir-autogen, elixir-format msgid "Invalid parameters" msgstr "" +#: lib/pleroma/web/common_api/utils.ex:360 #, elixir-autogen, elixir-format -#: lib/pleroma/web/common_api/utils.ex:353 msgid "Invalid password." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:267 +#, elixir-autogen, elixir-format msgid "Invalid request" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/twitter_api.ex:108 +#, elixir-autogen, elixir-format msgid "Kocaptcha service unavailable" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:142 +#, elixir-autogen, elixir-format msgid "Missing parameters" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:171 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:197 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:239 +#, elixir-autogen, elixir-format msgid "No such permission_group" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:502 #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 #: lib/pleroma/web/feed/tag_controller.ex:16 #: lib/pleroma/web/feed/user_controller.ex:69 #: lib/pleroma/web/o_status/o_status_controller.ex:132 -#: lib/pleroma/web/plugs/uploaded_media.ex:84 +#: lib/pleroma/web/plugs/uploaded_media.ex:104 +#, elixir-autogen, elixir-format msgid "Not found" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:324 +#, elixir-autogen, elixir-format msgid "Poll's author can't vote" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:499 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:39 @@ -243,213 +242,213 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:52 #: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:382 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 +#, elixir-autogen, elixir-format msgid "Record not found" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 #: lib/pleroma/web/feed/user_controller.ex:78 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:42 #: lib/pleroma/web/o_status/o_status_controller.ex:138 +#, elixir-autogen, elixir-format msgid "Something went wrong" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api/activity_draft.ex:143 +#, elixir-autogen, elixir-format msgid "The message visibility must be direct" msgstr "" +#: lib/pleroma/web/common_api/utils.ex:474 #, elixir-autogen, elixir-format -#: lib/pleroma/web/common_api/utils.ex:467 msgid "The status is over the character limit" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex:36 +#, elixir-autogen, elixir-format msgid "This resource requires authentication." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/rate_limiter.ex:208 +#, elixir-autogen, elixir-format msgid "Throttled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:354 +#, elixir-autogen, elixir-format msgid "Too many choices" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:268 +#, elixir-autogen, elixir-format msgid "You can't revoke your own admin status." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:243 #: lib/pleroma/web/o_auth/o_auth_controller.ex:333 +#, elixir-autogen, elixir-format msgid "Your account is currently disabled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:205 #: lib/pleroma/web/o_auth/o_auth_controller.ex:356 +#, elixir-autogen, elixir-format msgid "Your login is missing a confirmed e-mail address" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 +#, elixir-autogen, elixir-format msgid "can't read inbox of %{nickname} as %{as_nickname}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:489 +#, elixir-autogen, elixir-format msgid "can't update outbox of %{nickname} as %{as_nickname}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:526 +#, elixir-autogen, elixir-format msgid "conversation is already muted" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:508 +#, elixir-autogen, elixir-format msgid "error" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:34 +#, elixir-autogen, elixir-format msgid "mascots can only be images" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:63 +#, elixir-autogen, elixir-format msgid "not found" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:437 +#, elixir-autogen, elixir-format msgid "Bad OAuth request." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/twitter_api.ex:114 +#, elixir-autogen, elixir-format msgid "CAPTCHA already used" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/twitter_api.ex:111 +#, elixir-autogen, elixir-format msgid "CAPTCHA expired" msgstr "" +#: lib/pleroma/web/plugs/uploaded_media.ex:77 #, elixir-autogen, elixir-format -#: lib/pleroma/web/plugs/uploaded_media.ex:57 msgid "Failed" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:453 +#, elixir-autogen, elixir-format msgid "Failed to authenticate: %{message}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:484 +#, elixir-autogen, elixir-format msgid "Failed to set up user account." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/o_auth_scopes_plug.ex:37 +#, elixir-autogen, elixir-format msgid "Insufficient permissions: %{permissions}." msgstr "" +#: lib/pleroma/web/plugs/uploaded_media.ex:131 #, elixir-autogen, elixir-format -#: lib/pleroma/web/plugs/uploaded_media.ex:111 msgid "Internal Error" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/fallback_controller.ex:22 #: lib/pleroma/web/o_auth/fallback_controller.ex:29 +#, elixir-autogen, elixir-format msgid "Invalid Username/Password" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/twitter_api.ex:117 +#, elixir-autogen, elixir-format msgid "Invalid answer data" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 +#, elixir-autogen, elixir-format msgid "Nodeinfo schema version not handled" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:194 +#, elixir-autogen, elixir-format msgid "This action is outside the authorized scopes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/fallback_controller.ex:14 +#, elixir-autogen, elixir-format msgid "Unknown error, please check the details and try again." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:136 #: lib/pleroma/web/o_auth/o_auth_controller.ex:180 +#, elixir-autogen, elixir-format msgid "Unlisted redirect_uri." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:433 +#, elixir-autogen, elixir-format msgid "Unsupported OAuth provider: %{provider}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/uploaders/uploader.ex:74 +#, elixir-autogen, elixir-format msgid "Uploader callback timeout" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/uploader_controller.ex:23 +#, elixir-autogen, elixir-format msgid "bad request" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/twitter_api.ex:102 +#, elixir-autogen, elixir-format msgid "CAPTCHA Error" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:282 +#, elixir-autogen, elixir-format msgid "Could not add reaction emoji" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/common_api.ex:293 +#, elixir-autogen, elixir-format msgid "Could not remove reaction emoji" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/twitter_api.ex:128 +#, elixir-autogen, elixir-format msgid "Invalid CAPTCHA (Missing parameter: %{name})" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:96 +#, elixir-autogen, elixir-format msgid "List not found" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:153 +#, elixir-autogen, elixir-format msgid "Missing parameter: %{name}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:232 #: lib/pleroma/web/o_auth/o_auth_controller.ex:346 +#, elixir-autogen, elixir-format msgid "Password reset is required" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 @@ -529,81 +528,82 @@ msgstr "" #: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 #: lib/pleroma/web/uploader_controller.ex:6 #: lib/pleroma/web/web_finger/web_finger_controller.ex:6 +#, elixir-autogen, elixir-format msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/ensure_authenticated_plug.ex:32 +#, elixir-autogen, elixir-format msgid "Two-factor authentication enabled, you must use a access token." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 +#, elixir-autogen, elixir-format msgid "Web push subscription is disabled on this Pleroma instance" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:234 +#, elixir-autogen, elixir-format msgid "You can't revoke your own admin/moderator status." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:131 +#, elixir-autogen, elixir-format msgid "authorization required for timeline view" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 +#, elixir-autogen, elixir-format msgid "Access denied" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:331 +#, elixir-autogen, elixir-format msgid "This API requires an authenticated user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/user_is_admin_plug.ex:21 +#, elixir-autogen, elixir-format msgid "User is not an admin." msgstr "" -#, elixir-format #: lib/pleroma/user/backup.ex:73 +#, elixir-format msgid "Last export was less than a day ago" msgid_plural "Last export was less than %{days} days ago" msgstr[0] "" msgstr[1] "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:421 +#, elixir-autogen, elixir-format msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters" msgstr "" +#: lib/pleroma/web/common_api/utils.ex:489 #, elixir-autogen, elixir-format -#: lib/pleroma/web/common_api/utils.ex:482 msgid "Too many attachments" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/user_is_staff_plug.ex:20 +#, elixir-autogen, elixir-format msgid "User is not a staff member." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/o_auth/o_auth_controller.ex:366 +#, elixir-autogen, elixir-format msgid "Your account is awaiting approval." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:258 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:261 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:264 +#, elixir-autogen, elixir-format msgid "File is too large" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/plugs/ensure_privileged_plug.ex:21 #: lib/pleroma/web/plugs/ensure_privileged_plug.ex:34 #: lib/pleroma/web/plugs/ensure_privileged_plug.ex:41 +#, elixir-autogen, elixir-format msgid "User isn't privileged." msgstr "" diff --git a/priv/gettext/oauth_scopes.pot b/priv/gettext/oauth_scopes.pot index 5f7b425f3..50ad0dd9e 100644 --- a/priv/gettext/oauth_scopes.pot +++ b/priv/gettext/oauth_scopes.pot @@ -10,252 +10,212 @@ msgid "" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:read" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "follow" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "push" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "read" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "read:accounts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "read:backups" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "read:blocks" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "read:bookmarks" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "read:chats" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "read:favourites" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "read:filters" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "read:follows" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "read:lists" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "read:notifications" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "read:reports" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "read:search" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "read:statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:accounts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:blocks" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:bookmarks" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "write:chats" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "write:conversations" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:favourites" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:filters" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "write:follow" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "write:follows" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:lists" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:media" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:mutes" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "write:notifications" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "write:reports" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "write:statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:read:accounts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:read:chats" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:read:invites" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:read:media_proxy_caches" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:read:reports" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:read:statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write:accounts" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write:chats" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write:follows" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write:invites" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write:media_proxy_caches" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write:reports" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format msgid "admin:write:statuses" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/api_spec/scopes/translator.ex:5 -msgid "read:media" -msgstr "" - #, elixir-autogen, elixir-format -#: lib/pleroma/web/api_spec/scopes/translator.ex:5 msgid "read:mutes" msgstr "" diff --git a/priv/gettext/static_pages.pot b/priv/gettext/static_pages.pot index 4c372ac4d..208528239 100644 --- a/priv/gettext/static_pages.pot +++ b/priv/gettext/static_pages.pot @@ -10,553 +10,553 @@ msgid "" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9 +#, elixir-autogen, elixir-format msgctxt "remote follow authorization button" msgid "Authorize" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2 +#, elixir-autogen, elixir-format msgctxt "remote follow error" msgid "Error fetching user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "remote follow header" msgid "Remote follow" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "placeholder text for auth code entry" msgid "Authentication code" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10 +#, elixir-autogen, elixir-format msgctxt "placeholder text for password entry" msgid "Password" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "placeholder text for username entry" msgid "Username" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13 +#, elixir-autogen, elixir-format msgctxt "remote follow authorization button for login" msgid "Authorize" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12 +#, elixir-autogen, elixir-format msgctxt "remote follow authorization button for mfa" msgid "Authorize" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2 +#, elixir-autogen, elixir-format msgctxt "remote follow error" msgid "Error following account" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "remote follow header, need login" msgid "Log in to follow" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "remote follow mfa header" msgid "Two-factor authentication" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "remote follow success" msgid "Account followed!" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:7 #: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7 +#, elixir-autogen, elixir-format msgctxt "placeholder text for account id" msgid "Your account ID, e.g. lain@quitter.se" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "remote follow authorization button for following with a remote account" msgid "Follow" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2 +#, elixir-autogen, elixir-format msgctxt "remote follow error" msgid "Error: %{error}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "remote follow header" msgid "Remotely follow %{nickname}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12 +#, elixir-autogen, elixir-format msgctxt "password reset button" msgid "Reset" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "password reset failed homepage link" msgid "Homepage" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "password reset failed message" msgid "Password reset failed" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "password reset form confirm password prompt" msgid "Confirmation" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "password reset form password prompt" msgid "Password" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "password reset invalid token message" msgid "Invalid Token" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2 +#, elixir-autogen, elixir-format msgctxt "password reset successful homepage link" msgid "Homepage" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "password reset successful message" msgid "Password changed!" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/feed/feed/tag.atom.eex:12 #: lib/pleroma/web/templates/feed/feed/tag.rss.eex:8 +#, elixir-autogen, elixir-format msgctxt "tag feed description" msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "oauth authorization exists page title" msgid "Authorization exists" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:32 +#, elixir-autogen, elixir-format msgctxt "oauth authorize approve button" msgid "Approve" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:30 +#, elixir-autogen, elixir-format msgctxt "oauth authorize cancel button" msgid "Cancel" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:23 +#, elixir-autogen, elixir-format msgctxt "oauth authorize message" msgid "Application %{client_name} is requesting access to your account." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "oauth authorized page title" msgid "Successfully authorized" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "oauth external provider page title" msgid "Sign in with external provider" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13 +#, elixir-autogen, elixir-format msgctxt "oauth external provider sign in button" msgid "Sign in with %{strategy}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:54 +#, elixir-autogen, elixir-format msgctxt "oauth login button" msgid "Log In" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:51 +#, elixir-autogen, elixir-format msgctxt "oauth login password prompt" msgid "Password" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:47 +#, elixir-autogen, elixir-format msgctxt "oauth login username prompt" msgid "Username" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:39 +#, elixir-autogen, elixir-format msgctxt "oauth register nickname prompt" msgid "Pleroma Handle" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37 +#, elixir-autogen, elixir-format msgctxt "oauth register nickname unchangeable warning" msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18 +#, elixir-autogen, elixir-format msgctxt "oauth register page email prompt" msgid "Email" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10 +#, elixir-autogen, elixir-format msgctxt "oauth register page fill form prompt" msgid "If you'd like to register a new account, please provide the details below." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35 +#, elixir-autogen, elixir-format msgctxt "oauth register page login button" msgid "Proceed as existing user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31 +#, elixir-autogen, elixir-format msgctxt "oauth register page login password prompt" msgid "Password" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24 +#, elixir-autogen, elixir-format msgctxt "oauth register page login prompt" msgid "Alternatively, sign in to connect to existing account." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27 +#, elixir-autogen, elixir-format msgctxt "oauth register page login username prompt" msgid "Name or email" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14 +#, elixir-autogen, elixir-format msgctxt "oauth register page nickname prompt" msgid "Nickname" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22 +#, elixir-autogen, elixir-format msgctxt "oauth register page register button" msgid "Proceed as new user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "oauth register page title" msgid "Registration Details" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:36 +#, elixir-autogen, elixir-format msgctxt "oauth register page title" msgid "This is the first time you visit! Please enter your Pleroma handle." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2 +#, elixir-autogen, elixir-format msgctxt "oauth scopes message" msgid "The following permissions will be granted" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:2 #: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:2 +#, elixir-autogen, elixir-format msgctxt "oauth token code message" msgid "Token code is
%{token}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:12 +#, elixir-autogen, elixir-format msgctxt "mfa auth code prompt" msgid "Authentication code" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "mfa auth page title" msgid "Two-factor authentication" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:23 +#, elixir-autogen, elixir-format msgctxt "mfa auth page use recovery code link" msgid "Enter a two-factor recovery code" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:20 +#, elixir-autogen, elixir-format msgctxt "mfa auth verify code button" msgid "Verify" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "mfa recover page title" msgid "Two-factor recovery" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:12 +#, elixir-autogen, elixir-format msgctxt "mfa recover recovery code prompt" msgid "Recovery code" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:23 +#, elixir-autogen, elixir-format msgctxt "mfa recover use 2fa code link" msgid "Enter a two-factor code" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:20 +#, elixir-autogen, elixir-format msgctxt "mfa recover verify recovery code button" msgid "Verify" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "static fe profile page remote follow button" msgid "Remote follow" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/email/digest.html.eex:163 +#, elixir-autogen, elixir-format msgctxt "digest email header line" msgid "Hey %{nickname}, here is what you've missed!" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/email/digest.html.eex:544 +#, elixir-autogen, elixir-format msgctxt "digest email receiver address" msgid "The email address you are subscribed as is %{email}. " msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/email/digest.html.eex:538 +#, elixir-autogen, elixir-format msgctxt "digest email sending reason" msgid "You have received this email because you have signed up to receive digest emails from %{instance} Pleroma instance." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/email/digest.html.eex:547 +#, elixir-autogen, elixir-format msgctxt "digest email unsubscribe action" msgid "To unsubscribe, please go %{here}." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/email/digest.html.eex:547 +#, elixir-autogen, elixir-format msgctxt "digest email unsubscribe action link text" msgid "here" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "mailer unsubscribe failed message" msgid "UNSUBSCRIBE FAILURE" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1 +#, elixir-autogen, elixir-format msgctxt "mailer unsubscribe successful message" msgid "UNSUBSCRIBE SUCCESSFUL" msgstr "" -#, elixir-format #: lib/pleroma/web/templates/email/digest.html.eex:385 +#, elixir-format msgctxt "new followers count header" msgid "%{count} New Follower" msgid_plural "%{count} New Followers" msgstr[0] "" msgstr[1] "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:356 +#, elixir-autogen, elixir-format msgctxt "account archive email body - self-requested" msgid "

You requested a full backup of your Pleroma account. It's ready for download:

\n

%{download_url}

\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:384 +#, elixir-autogen, elixir-format msgctxt "account archive email subject" msgid "Your account archive is ready" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:188 +#, elixir-autogen, elixir-format msgctxt "approval pending email body" msgid "

Awaiting Approval

\n

Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.

\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:202 +#, elixir-autogen, elixir-format msgctxt "approval pending email subject" msgid "Your account is awaiting approval" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:158 +#, elixir-autogen, elixir-format msgctxt "confirmation email body" msgid "

Thank you for registering on %{instance_name}

\n

Email confirmation is required to activate the account.

\n

Please click the following link to activate your account.

\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:174 +#, elixir-autogen, elixir-format msgctxt "confirmation email subject" msgid "%{instance_name} account confirmation" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:310 +#, elixir-autogen, elixir-format msgctxt "digest email subject" msgid "Your digest from %{instance_name}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:81 +#, elixir-autogen, elixir-format msgctxt "password reset email body" msgid "

Reset your password at %{instance_name}

\n

Someone has requested password change for your account at %{instance_name}.

\n

If it was you, visit the following link to proceed: reset password.

\n

If it was someone else, nothing to worry about: your data is secure and your password has not been changed.

\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:98 +#, elixir-autogen, elixir-format msgctxt "password reset email subject" msgid "Password reset" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:215 +#, elixir-autogen, elixir-format msgctxt "successful registration email body" msgid "

Hello @%{nickname},

\n

Your account at %{instance_name} has been registered successfully.

\n

No further action is required to activate your account.

\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:231 +#, elixir-autogen, elixir-format msgctxt "successful registration email subject" msgid "Account registered on %{instance_name}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:119 +#, elixir-autogen, elixir-format msgctxt "user invitation email body" msgid "

You are invited to %{instance_name}

\n

%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.

\n

Click the following link to register: accept invitation.

\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:136 +#, elixir-autogen, elixir-format msgctxt "user invitation email subject" msgid "Invitation to %{instance_name}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:53 +#, elixir-autogen, elixir-format msgctxt "welcome email html body" msgid "Welcome to %{instance_name}!" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:41 +#, elixir-autogen, elixir-format msgctxt "welcome email subject" msgid "Welcome to %{instance_name}!" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:65 +#, elixir-autogen, elixir-format msgctxt "welcome email text body" msgid "Welcome to %{instance_name}!" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/emails/user_email.ex:368 +#, elixir-autogen, elixir-format msgctxt "account archive email body - admin requested" msgid "

Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:

\n

%{download_url}

\n" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/controllers/util_controller.ex:123 +#, elixir-autogen, elixir-format msgctxt "remote follow error message - unknown error" msgid "Something went wrong." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/controllers/util_controller.ex:67 +#, elixir-autogen, elixir-format msgctxt "remote follow error message - user not found" msgid "Could not find user" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:8 +#, elixir-autogen, elixir-format msgctxt "status interact authorization button" msgid "Interact" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:2 +#, elixir-autogen, elixir-format msgctxt "status interact error" msgid "Error: %{error}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/controllers/util_controller.ex:95 +#, elixir-autogen, elixir-format msgctxt "status interact error message - status not found" msgid "Could not find status" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/twitter_api/controllers/util_controller.ex:144 +#, elixir-autogen, elixir-format msgctxt "status interact error message - unknown error" msgid "Something went wrong." msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "status interact header" msgid "Interacting with %{nickname}'s %{status_link}" msgstr "" -#, elixir-autogen, elixir-format #: lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex:4 +#, elixir-autogen, elixir-format msgctxt "status interact header - status link text" msgid "status" msgstr "" From dd9f8150fce34cc9a30a92a7e3de9560d5146871 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 22 Jun 2023 21:24:58 +0200 Subject: [PATCH 0267/1008] Merge Revert "Merge branch 'validate-host' into 'develop'" This reverts commit d998a114e26033e98e87778e5ca659aff91831bf, reversing changes made to da6b4003acad84b0f60ad8da6d08cfe13564b058. --- changelog.d/3896.add | 1 - changelog.d/3909.skip | 0 lib/pleroma/web/plugs/uploaded_media.ex | 22 +--------------- .../web/plugs/uploaded_media_plug_test.exs | 26 ------------------- 4 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 changelog.d/3896.add create mode 100644 changelog.d/3909.skip diff --git a/changelog.d/3896.add b/changelog.d/3896.add deleted file mode 100644 index e8fde4c8a..000000000 --- a/changelog.d/3896.add +++ /dev/null @@ -1 +0,0 @@ -Validate Host header for Uploads and return a 302 if the base_url has changed diff --git a/changelog.d/3909.skip b/changelog.d/3909.skip new file mode 100644 index 000000000..e69de29bb diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 9dd5eb239..8b3bc9acb 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -46,32 +46,12 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do config = Pleroma.Config.get(Pleroma.Upload) - %{scheme: media_scheme, host: media_host, port: media_port} = - Pleroma.Upload.base_url() |> URI.parse() - - with {:valid_host, true} <- {:valid_host, match?(^media_host, conn.host)}, - uploader <- Keyword.fetch!(config, :uploader), + with uploader <- Keyword.fetch!(config, :uploader), proxy_remote = Keyword.get(config, :proxy_remote, false), {:ok, get_method} <- uploader.get_file(file), false <- media_is_banned(conn, get_method) do get_media(conn, get_method, proxy_remote, opts) else - {:valid_host, false} -> - redirect_url = - %URI{ - scheme: media_scheme, - host: media_host, - port: media_port, - path: conn.request_path, - query: conn.query_string - } - |> URI.to_string() - |> String.trim_trailing("?") - - conn - |> Phoenix.Controller.redirect(external: redirect_url) - |> halt() - _ -> conn |> send_resp(:internal_server_error, dgettext("errors", "Failed")) diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs index dbf8ca5ec..8323ff6ab 100644 --- a/test/pleroma/web/plugs/uploaded_media_plug_test.exs +++ b/test/pleroma/web/plugs/uploaded_media_plug_test.exs @@ -40,30 +40,4 @@ defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do &(&1 == {"content-disposition", ~s[inline; filename="\\"cofe\\".gif"]}) ) end - - test "denies access to media if wrong Host", %{ - attachment_url: attachment_url - } do - conn = get(build_conn(), attachment_url) - - assert conn.status == 200 - - new_media_base = "http://media.localhost:8080" - - %{scheme: new_media_scheme, host: new_media_host, port: new_media_port} = - URI.parse(new_media_base) - - clear_config([Pleroma.Upload, :base_url], new_media_base) - - conn = get(build_conn(), attachment_url) - - expected_url = - URI.parse(attachment_url) - |> Map.put(:host, new_media_host) - |> Map.put(:port, new_media_port) - |> Map.put(:scheme, new_media_scheme) - |> URI.to_string() - - assert redirected_to(conn, 302) == expected_url - end end From 8bc51288be21f3b04d74504dc0c7d5a7ac6aa0e3 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 27 Jun 2023 00:06:58 +0200 Subject: [PATCH 0268/1008] release_runtime_provider_test: Explicitely use non-existant config file --- changelog.d/testfix-system-config-use.skip | 0 test/pleroma/config/release_runtime_provider_test.exs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/testfix-system-config-use.skip diff --git a/changelog.d/testfix-system-config-use.skip b/changelog.d/testfix-system-config-use.skip new file mode 100644 index 000000000..e69de29bb diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs index 4e0d4c838..e6d10b13e 100644 --- a/test/pleroma/config/release_runtime_provider_test.exs +++ b/test/pleroma/config/release_runtime_provider_test.exs @@ -10,7 +10,7 @@ defmodule Pleroma.Config.ReleaseRuntimeProviderTest do describe "load/2" do test "loads release defaults config and warns about non-existent runtime config" do ExUnit.CaptureIO.capture_io(fn -> - merged = ReleaseRuntimeProvider.load([], []) + merged = ReleaseRuntimeProvider.load([], config_path: "/var/empty/config.exs") assert merged == Pleroma.Config.Holder.release_defaults() end) =~ "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file" From aa4c4ab2a0fd2e3ed3abd7ea455463f3f65a5083 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 27 Jun 2023 00:23:21 +0200 Subject: [PATCH 0269/1008] mix: 2023-06 deps update this fixes compatibility with Erlang OTP 26 Related: https://git.pleroma.social/pleroma/pleroma/-/issues/2913 --- changelog.d/2023-06-deps-update.skip | 0 mix.exs | 3 ++- mix.lock | 38 ++++++++++++++-------------- 3 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 changelog.d/2023-06-deps-update.skip diff --git a/changelog.d/2023-06-deps-update.skip b/changelog.d/2023-06-deps-update.skip new file mode 100644 index 000000000..e69de29bb diff --git a/mix.exs b/mix.exs index 62ac1c1a0..fa72a85a9 100644 --- a/mix.exs +++ b/mix.exs @@ -150,7 +150,8 @@ defmodule Pleroma.Mixfile do {:bbcode_pleroma, "~> 0.2.0"}, {:cors_plug, "~> 2.0"}, {:web_push_encryption, "~> 0.3.1"}, - {:swoosh, "~> 1.0"}, + # swoosh 1.11.2+ requires Elixir 1.12+ + {:swoosh, "~> 1.10.0"}, {:phoenix_swoosh, "~> 1.1"}, {:gen_smtp, "~> 0.13"}, {:ex_syslogger, "~> 1.4"}, diff --git a/mix.lock b/mix.lock index b6938f44b..fc1266570 100644 --- a/mix.lock +++ b/mix.lock @@ -15,23 +15,23 @@ "concurrent_limiter": {:hex, :concurrent_limiter, "0.1.1", "43ae1dc23edda1ab03dd66febc739c4ff710d047bb4d735754909f9a474ae01c", [:mix], [{:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "53968ff238c0fbb4d7ed76ddb1af0be6f3b2f77909f6796e249e737c505a16eb"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "2.0.3", "316f806d10316e6d10f09473f19052d20ba0a0ce2a1d910ddf57d663dac402ae", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ee4ae1418e6ce117fc42c2ba3e6cbdca4e95ecd2fe59a05ec6884ca16d469aea"}, - "covertool": {:hex, :covertool, "2.0.4", "54acff6cddd88d28dea663cd2e1fe20dd32fcf5f5d3aff7d59031ce44ce39efa", [:rebar3], [], "hexpm", "5c9568ba4308fda2082172737c80c31d991ea83961eb10791f06106a870d0cdc"}, - "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, + "covertool": {:hex, :covertool, "2.0.6", "4a291b4e3449025b0595d8f44c8d7635d4f48f033be2ce88d22a329f36f94a91", [:rebar3], [], "hexpm", "5db3fcd82180d8ea4ad857d4d1ab21a8d31b5aee0d60d2f6c0f9e25a411d1e21"}, + "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, - "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, - "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.22", "ea3e45c6359446dc308be0a64ce82a03260d973de7d0625a762e6d352ff57958", [:mix], [{:earmark_parser, "~> 1.4.23", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "1caf5145665a42fd76d5317286b0c171861fb1c04f86ab103dde76868814fdfb"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"}, - "ecto": {:hex, :ecto, "3.9.5", "9f0aa7ae44a1577b651c98791c6988cd1b69b21bc724e3fd67090b97f7604263", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d4f3115d8cbacdc0bfa4b742865459fb1371d0715515842a1fb17fe31920b74c"}, + "ecto": {:hex, :ecto, "3.10.2", "6b887160281a61aa16843e47735b8a266caa437f80588c3ab80a8a960e6abe37", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6a895778f0d7648a4b34b486af59a1c8009041fbdf2b17f1ac215eb829c60235"}, "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_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"}, - "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.11", "6e20144c1446dcccfcdb4c142c9d8b7992a90a569b1d5958cbea5458550b25f0", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "def61f1f92d4f40d51c80bbae2157212d6c0a459eb604be446e47369cbd40b23"}, + "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, "eimp": {:hex, :eimp, "1.0.14", "fc297f0c7e2700457a95a60c7010a5f1dcb768a083b6d53f49cd94ab95a28f22", [:rebar3], [{:p1_utils, "1.0.18", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm", "501133f3112079b92d9e22da8b88bf4f0e13d4d67ae9c15c42c30bd25ceb83b6"}, "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"}, @@ -48,10 +48,10 @@ "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.10.2", "9ad27d68270d879f73f26604bb2e573d40f29bf0e907064a9a337f90a16a0312", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd8b11b282072cec2ef30852283949c248bd5d2820c88d8acc89402b81db7550"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"}, + "floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, "gettext": {:hex, :gettext, "0.22.2", "6bfca374de34ecc913a28ba391ca184d88d77810a3e427afa8454a71a51341ac", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "8a2d389673aea82d7eae387e6a2ccc12660610080ae7beb19452cfdc1ec30f60"}, - "gun": {:hex, :gun, "2.0.0", "2326bc0fd6d9cf628419708270d6fe8b02b8d002cf992e4165a77d997b1defd0", [:make, :rebar3], [{:cowlib, "2.12.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "6613cb7c62930dc8d58263c44dda72f8556346ba88358fc929dcbc5f76d04569"}, + "gun": {:hex, :gun, "2.0.1", "160a9a5394800fcba41bc7e6d421295cf9a7894c2252c0678244948e3336ad73", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "a10bc8d6096b9502205022334f719cc9a08d9adcfbfc0dbee9ef31b56274a20b"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, @@ -67,31 +67,31 @@ "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, - "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"}, - "mogrify": {:hex, :mogrify, "0.9.2", "b360984adea7dd6a55f18028e6327973c58de7f548fdb86c9859848aa904d5b0", [:mix], [], "hexpm", "c18d10fd70ca20e2585301616c89f6e4f7159d92efc9cc8ee579e00c886f699d"}, + "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"}, + "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"}, "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, "nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"}, "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "oban": {:hex, :oban, "2.13.6", "a0cb1bce3bd393770512231fb5a3695fa19fd3af10d7575bf73f837aee7abf43", [:mix], [{:ecto_sql, "~> 3.6", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c1c5eb16f377b3cbbf2ea14be24d20e3d91285af9d1ac86260b7c2af5464887"}, - "open_api_spex": {:hex, :open_api_spex, "3.16.1", "8137c338129d63060b4b04947c6c57429f86267045c479c703a38a6d3f98dee1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "ef6fd778ac121af866b48b75ad4ad256b6ff33949113ea4aa1629af8bfdfdbfb"}, + "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"}, "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.5", "1495bb014be12c9a9252eca04b9af54246f6b5c1e4cd1f30210cd00ec540cf8e", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.7", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "ef4fa50dd78364409039c99cf6f98ab5209b4c5f8796c17f4db118324f0db852"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.17.14", "5ec615d4d61bf9d4755f158bd6c80372b715533fe6d6219e12d74fb5eedbeac1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.0 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "afeb6ba43ce329a6f7fc1c9acdfc6d3039995345f025febb7f409a92f6faebd3"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, @@ -101,7 +101,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.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, + "postgrex": {:hex, :postgrex, "0.17.1", "01c29fd1205940ee55f7addb8f1dc25618ca63a8817e56fac4f6846fc2cddcbe", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "14b057b488e73be2beee508fb1955d8db90d6485c6466428fe9ccf1d6692a555"}, "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, "prom_ex": {:hex, :prom_ex, "1.7.1", "39331ee3fe6f9a8587d8208bf9274a253bb80281700e127dd18786cda5e08c37", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.10.2", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.1", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0.2", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "4c978872b88a929833925a0f4d0561824804c671fdd04581e765509ed0a6ed08"}, "prometheus": {:hex, :prometheus, "4.10.0", "792adbf0130ff61b5fa8826f013772af24b6e57b984445c8d602c8a0355704a1", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "2a99bb6dce85e238c7236fde6b0064f9834dc420ddbd962aac4ea2a3c3d59384"}, @@ -115,10 +115,10 @@ "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"}, - "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, + "swoosh": {:hex, :swoosh, "1.10.3", "32f1531ee3fe4e82da8175c597bf3692938f8152eb981e0cbf57107b6c5924c1", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8b7167d93047bac6e1a1c367bf7d899cf2e4fea0592ee04a70673548ef6091b9"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"}, "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, From 9e69adf76fcd8c13581dcd1abc68520f3f0e25d8 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 27 Jun 2023 01:52:40 +0200 Subject: [PATCH 0270/1008] mix: Remove override on plug --- mix.exs | 3 --- mix.lock | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/mix.exs b/mix.exs index fa72a85a9..fc4e87ee3 100644 --- a/mix.exs +++ b/mix.exs @@ -194,9 +194,6 @@ defmodule Pleroma.Mixfile do {:open_api_spex, "~> 3.16"}, {:ecto_psql_extras, "~> 0.6"}, - # indirect dependency version override - {:plug, "~> 1.10.4", override: true}, - ## dev & test {:ex_doc, "~> 0.22", only: :dev, runtime: false}, {:ex_machina, "~> 2.4", only: :test}, diff --git a/mix.lock b/mix.lock index fc1266570..da187ac5c 100644 --- a/mix.lock +++ b/mix.lock @@ -95,7 +95,7 @@ "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, - "plug": {:hex, :plug, "1.10.4", "41eba7d1a2d671faaf531fa867645bd5a3dce0957d8e2a3f398ccff7d2ef017f", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ad1e233fe73d2eec56616568d260777b67f53148a999dc2d048f4eb9778fe4a0"}, + "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "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"}, From 3a67b8f28728d3071c9a64d9b8c07ab91919676f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 27 Jun 2023 02:08:49 +0200 Subject: [PATCH 0271/1008] endpoint: Use custom Multipart module for dynamic configuration --- lib/pleroma/web/endpoint.ex | 7 ++----- lib/pleroma/web/multipart.ex | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 lib/pleroma/web/multipart.ex diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index d8d40cceb..574f3ab63 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -101,13 +101,10 @@ defmodule Pleroma.Web.Endpoint do plug(Plug.Logger, log: :debug) plug(Plug.Parsers, - parsers: [ - :urlencoded, - {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}}, - :json - ], + parsers: [:urlencoded, Pleroma.Web.Multipart, :json], pass: ["*/*"], json_decoder: Jason, + # Note: this is compile-time only, won't work for database-config length: Config.get([:instance, :upload_limit]), body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} ) diff --git a/lib/pleroma/web/multipart.ex b/lib/pleroma/web/multipart.ex new file mode 100644 index 000000000..e24bb14c2 --- /dev/null +++ b/lib/pleroma/web/multipart.ex @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +# +defmodule Pleroma.Web.Multipart do + @multipart Plug.Parsers.MULTIPART + + def init(opts) do + opts + end + + def parse(conn, "multipart", subtype, headers, opts) do + length = Pleroma.Config.get([:instance, :upload_limit]) + opts = @multipart.init([length: length] ++ opts) + @multipart.parse(conn, "multipart", subtype, headers, opts) + end + + def parse(conn, _type, _subtype, _headers, _opts) do + {:next, conn} + end +end From d7e049d5e833cebb0a67c771943d33c532f8923a Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 27 Jun 2023 02:48:50 +0200 Subject: [PATCH 0272/1008] router: Fix usage of globs warning: doing a prefix match with globs is deprecated, invalid segment "pleroma*path". You can either replace by a single segment match: /foo/bar-:var Or by mixing single segment match with globs: /foo/bar-:var/*rest --- lib/pleroma/web/router.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c1a690e28..6b9e158a3 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -996,8 +996,8 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web.Fallback do get("/registration/:token", RedirectController, :registration_page) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) - match(:*, "/api/pleroma*path", LegacyPleromaApiRerouterPlug, []) - get("/api*path", RedirectController, :api_not_implemented) + match(:*, "/api/pleroma/*path", LegacyPleromaApiRerouterPlug, []) + get("/api/*path", RedirectController, :api_not_implemented) get("/*path", RedirectController, :redirector_with_preload) options("/*path", RedirectController, :empty) From bf2b4b9400a44bfec02db9210a4ebc6d2d0f8553 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 27 Jun 2023 21:08:47 +0200 Subject: [PATCH 0273/1008] README.md: Update packaging state (GURU, AUR) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 62f8fdc64..2837b6ef8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ If your platform is not supported, or you just want to be able to edit the sourc - [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/) ### OS/Distro packages -Currently Pleroma is packaged for [YunoHost](https://yunohost.org) and [NixOS](https://nixos.org). If you want to package Pleroma for any OS/Distros, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**. +Currently Pleroma is packaged for [YunoHost](https://yunohost.org), [NixOS](https://nixos.org), [Gentoo through GURU](https://gentoo.org/) and [Archlinux through AUR](https://aur.archlinux.org/packages/pleroma). You may find more at . +If you want to package Pleroma for any OS/Distros, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**. ### Docker While we don’t provide docker files, other people have written very good ones. Take a look at or . From 63b9f767824b47d52e615d14353d5b9a7e71929a Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Jul 2023 23:25:04 -0400 Subject: [PATCH 0274/1008] Force the use of amd64 runners for jobs using ci-base --- .gitlab-ci.yml | 28 +++++++++++++++++++++++----- changelog.d/amd64-runner.skip | 0 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 changelog.d/amd64-runner.skip diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8daa9f434..148cbe405 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -65,13 +65,21 @@ check-changelog: - "**/*.exs" - "mix.lock" +.using-ci-base: + tags: + - amd64 + build: - extends: .build_changes_policy + extends: + - .build_changes_policy + - .using-ci-base stage: build script: - mix compile --force spec-build: + extends: + - .using-ci-base stage: test rules: - changes: @@ -85,6 +93,8 @@ spec-build: - mix pleroma.openapi_spec spec.json benchmark: + extends: + - .using-ci-base stage: benchmark when: manual variables: @@ -99,7 +109,9 @@ benchmark: - mix pleroma.load_testing unit-testing: - extends: .build_changes_policy + extends: + - .build_changes_policy + - .using-ci-base stage: test cache: &testing_cache_policy <<: *global_cache_policy @@ -121,7 +133,9 @@ unit-testing: path: coverage.xml unit-testing-erratic: - extends: .build_changes_policy + extends: + - .build_changes_policy + - .using-ci-base stage: test retry: 2 allow_failure: true @@ -155,7 +169,9 @@ unit-testing-erratic: # - mix test --trace --only federated unit-testing-rum: - extends: .build_changes_policy + extends: + - .build_changes_policy + - .using-ci-base stage: test cache: *testing_cache_policy services: @@ -186,7 +202,9 @@ lint: - mix format --check-formatted analysis: - extends: .build_changes_policy + extends: + - .build_changes_policy + - .using-ci-base stage: test cache: *testing_cache_policy script: diff --git a/changelog.d/amd64-runner.skip b/changelog.d/amd64-runner.skip new file mode 100644 index 000000000..e69de29bb From a1621839cc31a92e346cbd6065c4db6a8ebeb5a9 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 2 Jul 2023 11:03:09 -0400 Subject: [PATCH 0275/1008] Fix user fetch completely broken if featured collection is not in a supported form --- .../featured-collection-shouldnt-break-user-fetch.fix | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 5 +++++ test/pleroma/web/activity_pub/activity_pub_test.exs | 8 ++++++++ 3 files changed, 14 insertions(+) create mode 100644 changelog.d/featured-collection-shouldnt-break-user-fetch.fix diff --git a/changelog.d/featured-collection-shouldnt-break-user-fetch.fix b/changelog.d/featured-collection-shouldnt-break-user-fetch.fix new file mode 100644 index 000000000..e8ce288cc --- /dev/null +++ b/changelog.d/featured-collection-shouldnt-break-user-fetch.fix @@ -0,0 +1 @@ +Fix user fetch completely broken if featured collection is not in a supported form diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1a10aef1d..c93288b79 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1720,6 +1720,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end) end + def pin_data_from_featured_collection(obj) do + Logger.error("Could not parse featured collection #{inspect(obj)}") + %{} + end + def fetch_and_prepare_featured_from_ap_id(nil) do {:ok, %{}} end diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 54fc6ef0a..1e8c14043 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -2652,4 +2652,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew") assert user.name == " " end + + test "pin_data_from_featured_collection will ignore unsupported values" do + assert %{} == + ActivityPub.pin_data_from_featured_collection(%{ + "type" => "OrderedCollection", + "first" => "https://social.example/users/alice/collections/featured?page=true" + }) + end end From 6e4de2383f17810a35a32ccdfea8e9de0183dab4 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 2 Jul 2023 11:15:34 -0400 Subject: [PATCH 0276/1008] Fix handling report from a deactivated user --- .../handle-report-from-deactivated-user.fix | 1 + lib/pleroma/web/common_api.ex | 2 +- .../controllers/report_controller_test.exs | 21 +++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 changelog.d/handle-report-from-deactivated-user.fix diff --git a/changelog.d/handle-report-from-deactivated-user.fix b/changelog.d/handle-report-from-deactivated-user.fix new file mode 100644 index 000000000..6692d1aa8 --- /dev/null +++ b/changelog.d/handle-report-from-deactivated-user.fix @@ -0,0 +1 @@ +Fix handling report from a deactivated user diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 65d08de49..77b3fa5d2 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -583,7 +583,7 @@ defmodule Pleroma.Web.CommonAPI do end def update_report_state(activity_id, state) do - with %Activity{} = activity <- Activity.get_by_id(activity_id) do + with %Activity{} = activity <- Activity.get_by_id(activity_id, filter: []) do Utils.update_report_state(activity, state) else nil -> {:error, :not_found} diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs index c141cf69d..fb2579a3d 100644 --- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs @@ -123,6 +123,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do }) %{ + reporter: reporter, id: report_id, second_report_id: second_report_id } @@ -266,6 +267,26 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do assert ModerationLog.get_log_entry_message(second_log_entry) == "@#{admin.nickname} updated report ##{second_report_id} (on user @#{second_activity.user_actor.nickname}) with 'closed' state" end + + test "works if reporter is deactivated", %{ + conn: conn, + id: id, + reporter: reporter + } do + Pleroma.User.set_activation(reporter, false) + + conn + |> put_req_header("content-type", "application/json") + |> patch("/api/pleroma/admin/reports", %{ + "reports" => [ + %{"state" => "resolved", "id" => id} + ] + }) + |> json_response_and_validate_schema(:no_content) + + activity = Activity.get_by_id_with_user_actor(id) + assert activity.data["state"] == "resolved" + end end describe "GET /api/pleroma/admin/reports" do From 10249d1e42fd809ab75880c891f00ca1f3318f80 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 4 Jul 2023 03:03:41 +0200 Subject: [PATCH 0277/1008] CI: Let curl return non-0 on http failure code Otherwise it silently fails --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 148cbe405..81fd3d682 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -230,7 +230,7 @@ docs-deploy: before_script: - apk add curl script: - - curl -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline + - curl --fail-with-body -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline review_app: image: alpine:3.9 stage: deploy @@ -271,7 +271,7 @@ spec-deploy: before_script: - apk add curl script: - - curl -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline + - curl --fail-with-body -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline stop_review_app: From 8c3363a5e778899fe137273c2d188ebd3b9224fb Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 4 Jul 2023 03:23:32 +0200 Subject: [PATCH 0278/1008] CI: Use CI_JOB_TOKEN for cross-repo pipeline triggers --- .gitlab-ci.yml | 4 ++-- changelog.d/pipeline-triggers.skip | 0 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changelog.d/pipeline-triggers.skip diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 81fd3d682..cd0561852 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -230,7 +230,7 @@ docs-deploy: before_script: - apk add curl script: - - curl --fail-with-body -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline + - curl --fail-with-body -X POST -F"token=$CI_JOB_TOKEN" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline review_app: image: alpine:3.9 stage: deploy @@ -271,7 +271,7 @@ spec-deploy: before_script: - apk add curl script: - - curl --fail-with-body -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline + - curl --fail-with-body -X POST -F"token=$CI_JOB_TOKEN" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline stop_review_app: diff --git a/changelog.d/pipeline-triggers.skip b/changelog.d/pipeline-triggers.skip new file mode 100644 index 000000000..e69de29bb From 3d79ceb23a3dc9630d38807cf6e8a62a56f29d3b Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 4 Jul 2023 03:34:51 +0200 Subject: [PATCH 0279/1008] Deprecate audio scrobbling --- changelog.d/deprecate-scrobbles.remove | 1 + docs/development/API/pleroma_api.md | 6 ++++++ .../web/api_spec/operations/pleroma_scrobble_operation.ex | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 changelog.d/deprecate-scrobbles.remove diff --git a/changelog.d/deprecate-scrobbles.remove b/changelog.d/deprecate-scrobbles.remove new file mode 100644 index 000000000..c453a9784 --- /dev/null +++ b/changelog.d/deprecate-scrobbles.remove @@ -0,0 +1 @@ +Deprecate Pleroma's audio scrobbling diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md index 47fcb7479..bd0e07f9e 100644 --- a/docs/development/API/pleroma_api.md +++ b/docs/development/API/pleroma_api.md @@ -577,6 +577,9 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa 404 if the pack does not exist ## `GET /api/v1/pleroma/accounts/:id/scrobbles` + +Audio scrobbling in Pleroma is **deprecated**. + ### Requests a list of current and recent Listen activities for an account * Method `GET` * Authentication: not required @@ -598,6 +601,9 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa ``` ## `POST /api/v1/pleroma/scrobble` + +Audio scrobbling in Pleroma is **deprecated**. + ### Creates a new Listen activity for an account * Method `POST` * Authentication: required diff --git a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex index b6273bfcf..ca40da930 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex @@ -22,6 +22,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do summary: "Creates a new Listen activity for an account", security: [%{"oAuth" => ["write"]}], operationId: "PleromaAPI.ScrobbleController.create", + deprecated: true, requestBody: request_body("Parameters", create_request(), requried: true), responses: %{ 200 => Operation.response("Scrobble", "application/json", scrobble()) @@ -34,6 +35,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do tags: ["Scrobbles"], summary: "Requests a list of current and recent Listen activities for an account", operationId: "PleromaAPI.ScrobbleController.index", + deprecated: true, parameters: [ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} | pagination_params() ], From 0c3709173fe7905c3d7e9d542d3b1007d8f97042 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 4 Jul 2023 04:19:25 +0200 Subject: [PATCH 0280/1008] docs: Fix broken links --- changelog.d/gentoo_otp_hotfix.skip | 0 docs/installation/gentoo_en.md | 2 +- docs/installation/gentoo_otp_en.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changelog.d/gentoo_otp_hotfix.skip diff --git a/changelog.d/gentoo_otp_hotfix.skip b/changelog.d/gentoo_otp_hotfix.skip new file mode 100644 index 000000000..e69de29bb diff --git a/docs/installation/gentoo_en.md b/docs/installation/gentoo_en.md index 273e21d4b..87128d6f6 100644 --- a/docs/installation/gentoo_en.md +++ b/docs/installation/gentoo_en.md @@ -2,7 +2,7 @@ {! backend/installation/otp_vs_from_source.include !} -This guide covers a manual from-source installation. To use the gentoo package, please check the [packaged installation guide for gentoo](./gentoo.en.md). +This guide covers a manual from-source installation. To use the gentoo package, please check the [packaged installation guide for gentoo](./gentoo_otp_en.md). ## Installation diff --git a/docs/installation/gentoo_otp_en.md b/docs/installation/gentoo_otp_en.md index 5dcfe0b7b..76968eb38 100644 --- a/docs/installation/gentoo_otp_en.md +++ b/docs/installation/gentoo_otp_en.md @@ -65,7 +65,7 @@ The output from emerging postgresql should give you a command for initializing t ### Install media / graphics packages (optional) -See [Optional software packages needed for specific functionality](docs/installation/optional/media_graphics_packages.md) for details. +See [Optional software packages needed for specific functionality](optional/media_graphics_packages.md) for details. ```shell # emerge --ask media-video/ffmpeg media-gfx/imagemagick media-libs/exiftool From 28ff828caaada329283a37feb3940b543ef260a3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 28 Feb 2023 10:40:24 -0500 Subject: [PATCH 0281/1008] Add emoji policy to remove emojis matching certain urls https://git.pleroma.social/pleroma/pleroma/-/issues/2775 --- config/config.exs | 6 + lib/pleroma/web/activity_pub/emoji_policy.ex | 158 ++++++++++++++++++ .../activity_pub/mrf/emoji_policy_test.exs | 123 ++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/emoji_policy.ex create mode 100644 test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs diff --git a/config/config.exs b/config/config.exs index a92ab574b..ebcbf8b49 100644 --- a/config/config.exs +++ b/config/config.exs @@ -408,6 +408,12 @@ config :pleroma, :mrf_keyword, federated_timeline_removal: [], replace: [] +config :pleroma, :mrf_emoji, + remove_url: [], + remove_shortcode: [], + federated_timeline_removal_url: [], + federated_timeline_removal_shortcode: [] + config :pleroma, :mrf_hashtag, sensitive: ["nsfw"], reject: [], diff --git a/lib/pleroma/web/activity_pub/emoji_policy.ex b/lib/pleroma/web/activity_pub/emoji_policy.ex new file mode 100644 index 000000000..747e720b6 --- /dev/null +++ b/lib/pleroma/web/activity_pub/emoji_policy.ex @@ -0,0 +1,158 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do + require Pleroma.Constants + + @moduledoc "Reject or force-unlisted emojis with certain URLs or names" + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + defp config_remove_url do + Pleroma.Config.get([:mrf_emoji, :remove_url], []) + end + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def filter(%{"type" => type, "object" => %{} = object} = message) + when type in ["Create", "Update"] do + with object <- process_remove(object, :url, config_remove_url()) do + {:ok, Map.put(message, "object", object)} + end + end + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do + with object <- process_remove(object, :url, config_remove_url()) do + {:ok, object} + end + end + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def filter(message) do + {:ok, message} + end + + defp match_string?(string, pattern) when is_binary(pattern) do + string == pattern + end + + defp match_string?(string, %Regex{} = pattern) do + String.match?(string, pattern) + end + + defp match_any?(string, patterns) do + Enum.any?(patterns, &match_string?(string, &1)) + end + + defp process_remove(object, :url, patterns) do + processed_tag = + Enum.filter( + object["tag"], + fn + %{"type" => "Emoji", "icon" => %{"url" => url}} when is_binary(url) -> + not match_any?(url, patterns) + + _ -> + true + end + ) + + processed_emoji = + if object["emoji"] do + object["emoji"] + |> Enum.reduce(%{}, fn {name, url}, acc -> + if not match_any?(url, patterns) do + Map.put(acc, name, url) + else + acc + end + end) + else + nil + end + + if processed_emoji do + object + |> Map.put("tag", processed_tag) + |> Map.put("emoji", processed_emoji) + else + object + |> Map.put("tag", processed_tag) + end + end + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def describe do + # This horror is needed to convert regex sigils to strings + mrf_emoji = + Pleroma.Config.get(:mrf_emoji, []) + |> Enum.map(fn {key, value} -> + {key, + Enum.map(value, fn + pattern -> + if not is_binary(pattern) do + inspect(pattern) + else + pattern + end + end)} + end) + |> Enum.into(%{}) + + {:ok, %{mrf_emoji: mrf_emoji}} + end + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def config_description do + %{ + key: :mrf_emoji, + related_policy: "Pleroma.Web.ActivityPub.MRF.EmojiPolicy", + label: "MRF Emoji", + description: + "Reject or force-unlisted emojis whose URLs or names match a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).", + children: [ + %{ + key: :remove_url, + type: {:list, :string}, + description: """ + A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to both statuses and user profiles. + + Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. + """, + suggestions: ["foo", ~r/foo/iu] + }, + %{ + key: :remove_shortcode, + type: {:list, :string}, + description: """ + A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to both statuses and user profiles. + + Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. + """, + suggestions: ["foo", ~r/foo/iu] + }, + %{ + key: :federated_timeline_removal_url, + type: {:list, :string}, + description: """ + A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. + + Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. + """, + suggestions: ["foo", ~r/foo/iu] + }, + %{ + key: :federated_timeline_removal_shortcode, + type: {:list, :string}, + description: """ + A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. + + Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. + """, + suggestions: ["foo", ~r/foo/iu] + } + ] + } + end +end diff --git a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs new file mode 100644 index 000000000..fe1fb338e --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs @@ -0,0 +1,123 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do + use Pleroma.DataCase + + alias Pleroma.Web.ActivityPub.MRF + alias Pleroma.Web.ActivityPub.MRF.EmojiPolicy + + setup do: clear_config(:mrf_emoji) + + setup do + clear_config([:mrf_emoji], %{ + remove_url: [], + remove_shortcode: [], + federated_timeline_removal_url: [], + federated_timeline_removal_shortcode: [] + }) + end + + @emoji_tags [ + %{ + "icon" => %{ + "type" => "Image", + "url" => "https://example.org/emoji/biribiri/mikoto_smile2.png" + }, + "id" => "https://example.org/emoji/biribiri/mikoto_smile2.png", + "name" => ":mikoto_smile2:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + }, + %{ + "icon" => %{ + "type" => "Image", + "url" => "https://example.org/emoji/biribiri/mikoto_smile3.png" + }, + "id" => "https://example.org/emoji/biribiri/mikoto_smile3.png", + "name" => ":mikoto_smile3:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + }, + %{ + "icon" => %{ + "type" => "Image", + "url" => "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png" + }, + "id" => "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png", + "name" => ":nekomimi_girl_emoji_007:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + }, + %{ + "icon" => %{ + "type" => "Image", + "url" => "https://example.org/test.png" + }, + "id" => "https://example.org/test.png", + "name" => ":test:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + } + ] + + @misc_tags [%{"type" => "Placeholder"}] + + @user_data %{ + "type" => "Person", + "id" => "https://example.org/placeholder", + "name" => "lol", + "tag" => @emoji_tags ++ @misc_tags + } + + @status_data %{ + "type" => "Create", + "object" => %{ + "type" => "Note", + "id" => "https://example.org/placeholder", + "content" => "lol", + "tag" => @emoji_tags ++ @misc_tags, + "emoji" => %{ + "mikoto_smile2" => "https://example.org/emoji/biribiri/mikoto_smile2.png", + "mikoto_smile3" => "https://example.org/emoji/biribiri/mikoto_smile3.png", + "nekomimi_girl_emoji_007" => + "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png", + "test" => "https://example.org/test.png" + } + } + } + + describe "remove_url" do + setup do + clear_config([:mrf_emoji, :remove_url], [ + "https://example.org/test.png", + ~r{/biribiri/mikoto_smile[23]\.png}, + "nekomimi_girl_emoji" + ]) + + :ok + end + + test "processes user" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @user_data) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + assert %{"tag" => ^expected_tags} = filtered + end + + test "processes status" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + expected_emoji = %{ + "nekomimi_girl_emoji_007" => + "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png" + } + + assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered + end + end +end From 80ce6482f6eb85f4c1d172811affc4a17d24985a Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 28 Feb 2023 10:51:56 -0500 Subject: [PATCH 0282/1008] EmojiPolicy: implement remove by shortcode --- lib/pleroma/web/activity_pub/emoji_policy.ex | 48 ++++++++++++++++--- .../activity_pub/mrf/emoji_policy_test.exs | 33 +++++++++++++ 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/activity_pub/emoji_policy.ex b/lib/pleroma/web/activity_pub/emoji_policy.ex index 747e720b6..346b64699 100644 --- a/lib/pleroma/web/activity_pub/emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/emoji_policy.ex @@ -13,17 +13,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do Pleroma.Config.get([:mrf_emoji, :remove_url], []) end + defp config_remove_shortcode do + Pleroma.Config.get([:mrf_emoji, :remove_shortcode], []) + end + @impl Pleroma.Web.ActivityPub.MRF.Policy def filter(%{"type" => type, "object" => %{} = object} = message) when type in ["Create", "Update"] do - with object <- process_remove(object, :url, config_remove_url()) do + with object <- process_remove(object, :url, config_remove_url()), + object <- process_remove(object, :shortcode, config_remove_shortcode()) do {:ok, Map.put(message, "object", object)} end end @impl Pleroma.Web.ActivityPub.MRF.Policy def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do - with object <- process_remove(object, :url, config_remove_url()) do + with object <- process_remove(object, :url, config_remove_url()), + object <- process_remove(object, :shortcode, config_remove_shortcode()) do {:ok, object} end end @@ -46,12 +52,42 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do end defp process_remove(object, :url, patterns) do + process_remove_impl( + object, + fn + %{"icon" => %{"url" => url}} -> url + _ -> nil + end, + fn {_name, url} -> url end, + patterns + ) + end + + defp process_remove(object, :shortcode, patterns) do + process_remove_impl( + object, + fn + %{"name" => name} when is_binary(name) -> String.trim(name, ":") + _ -> nil + end, + fn {name, _url} -> name end, + patterns + ) + end + + defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do processed_tag = Enum.filter( object["tag"], fn - %{"type" => "Emoji", "icon" => %{"url" => url}} when is_binary(url) -> - not match_any?(url, patterns) + %{"type" => "Emoji"} = tag -> + str = extract_from_tag.(tag) + + if is_binary(str) do + not match_any?(str, patterns) + else + true + end _ -> true @@ -61,8 +97,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do processed_emoji = if object["emoji"] do object["emoji"] - |> Enum.reduce(%{}, fn {name, url}, acc -> - if not match_any?(url, patterns) do + |> Enum.reduce(%{}, fn {name, url} = emoji, acc -> + if not match_any?(extract_from_emoji.(emoji), patterns) do Map.put(acc, name, url) else acc diff --git a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs index fe1fb338e..6511f6532 100644 --- a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs @@ -120,4 +120,37 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered end end + + describe "remove_shortcode" do + setup do + clear_config([:mrf_emoji, :remove_shortcode], [ + "test", + ~r{mikoto_s}, + "nekomimi_girl_emoji" + ]) + + :ok + end + + test "processes user" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @user_data) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + assert %{"tag" => ^expected_tags} = filtered + end + + test "processes status" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + expected_emoji = %{ + "nekomimi_girl_emoji_007" => + "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png" + } + + assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered + end + end end From 7eb8abf7bb50e6bf4c6587ea308e1a52d944375a Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 28 Feb 2023 11:47:53 -0500 Subject: [PATCH 0283/1008] EmojiPolicy: Implement delist --- lib/pleroma/web/activity_pub/emoji_policy.ex | 117 ++++++++++-- .../activity_pub/mrf/emoji_policy_test.exs | 180 +++++++++++++++++- 2 files changed, 274 insertions(+), 23 deletions(-) diff --git a/lib/pleroma/web/activity_pub/emoji_policy.ex b/lib/pleroma/web/activity_pub/emoji_policy.ex index 346b64699..df1566ec3 100644 --- a/lib/pleroma/web/activity_pub/emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/emoji_policy.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do require Pleroma.Constants + alias Pleroma.Object.Updater + @moduledoc "Reject or force-unlisted emojis with certain URLs or names" @behaviour Pleroma.Web.ActivityPub.MRF.Policy @@ -17,12 +19,31 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do Pleroma.Config.get([:mrf_emoji, :remove_shortcode], []) end + defp config_unlist_url do + Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_url], []) + end + + defp config_unlist_shortcode do + Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], []) + end + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def history_awareness, do: :manual + @impl Pleroma.Web.ActivityPub.MRF.Policy def filter(%{"type" => type, "object" => %{} = object} = message) when type in ["Create", "Update"] do - with object <- process_remove(object, :url, config_remove_url()), - object <- process_remove(object, :shortcode, config_remove_shortcode()) do - {:ok, Map.put(message, "object", object)} + with {:ok, object} <- + Updater.do_with_history(object, fn object -> + {:ok, process_remove(object, :url, config_remove_url())} + end), + {:ok, object} <- + Updater.do_with_history(object, fn object -> + {:ok, process_remove(object, :shortcode, config_remove_shortcode())} + end), + activity <- Map.put(message, "object", object), + activity <- maybe_delist(activity) do + {:ok, activity} end end @@ -51,28 +72,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do Enum.any?(patterns, &match_string?(string, &1)) end + defp url_from_tag(%{"icon" => %{"url" => url}}), do: url + defp url_from_tag(_), do: nil + + defp url_from_emoji({_name, url}), do: url + + defp shortcode_from_tag(%{"name" => name}) when is_binary(name), do: String.trim(name, ":") + defp shortcode_from_tag(_), do: nil + + defp shortcode_from_emoji({name, _url}), do: name + defp process_remove(object, :url, patterns) do - process_remove_impl( - object, - fn - %{"icon" => %{"url" => url}} -> url - _ -> nil - end, - fn {_name, url} -> url end, - patterns - ) + process_remove_impl(object, &url_from_tag/1, &url_from_emoji/1, patterns) end defp process_remove(object, :shortcode, patterns) do - process_remove_impl( - object, - fn - %{"name" => name} when is_binary(name) -> String.trim(name, ":") - _ -> nil - end, - fn {name, _url} -> name end, - patterns - ) + process_remove_impl(object, &shortcode_from_tag/1, &shortcode_from_emoji/1, patterns) end defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do @@ -118,6 +133,66 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do end end + defp maybe_delist(%{"object" => object, "to" => to, "type" => "Create"} = activity) do + check = fn object -> + if any_emoji_match?(object, &url_from_tag/1, &url_from_emoji/1, config_unlist_url()) or + any_emoji_match?( + object, + &shortcode_from_tag/1, + &shortcode_from_emoji/1, + config_unlist_shortcode() + ) do + {:should_delist, nil} + else + {:ok, %{}} + end + end + + should_delist? = fn object -> + with {:ok, _} <- Pleroma.Object.Updater.do_with_history(object, check) do + false + else + _ -> true + end + end + + if Pleroma.Constants.as_public() in to and should_delist?.(object) do + to = List.delete(to, Pleroma.Constants.as_public()) + cc = [Pleroma.Constants.as_public() | activity["cc"] || []] + + activity + |> Map.put("to", to) + |> Map.put("cc", cc) + else + activity + end + end + + defp maybe_delist(activity), do: activity + + defp any_emoji_match?(object, extract_from_tag, extract_from_emoji, patterns) do + Kernel.||( + Enum.any?( + object["tag"], + fn + %{"type" => "Emoji"} = tag -> + str = extract_from_tag.(tag) + + if is_binary(str) do + match_any?(str, patterns) + else + false + end + + _ -> + false + end + ), + object["emoji"] + |> Enum.any?(fn emoji -> match_any?(extract_from_emoji.(emoji), patterns) end) + ) + end + @impl Pleroma.Web.ActivityPub.MRF.Policy def describe do # This horror is needed to convert regex sigils to strings diff --git a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs index 6511f6532..1e1b5c578 100644 --- a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs @@ -5,6 +5,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do use Pleroma.DataCase + require Pleroma.Constants + alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF.EmojiPolicy @@ -84,8 +86,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do "nekomimi_girl_emoji_007" => "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png", "test" => "https://example.org/test.png" - } - } + }, + "to" => ["https://example.org/self", Pleroma.Constants.as_public()], + "cc" => ["https://example.org/someone"] + }, + "to" => ["https://example.org/self", Pleroma.Constants.as_public()], + "cc" => ["https://example.org/someone"] + } + + @status_data_with_history %{ + "type" => "Create", + "object" => + @status_data["object"] + |> Map.merge(%{ + "formerRepresentations" => %{ + "type" => "OrderedCollection", + "orderedItems" => [@status_data["object"] |> Map.put("content", "older")], + "totalItems" => 1 + } + }), + "to" => ["https://example.org/self", Pleroma.Constants.as_public()], + "cc" => ["https://example.org/someone"] } describe "remove_url" do @@ -119,6 +140,49 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered end + + test "processes status with history" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data_with_history) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + expected_emoji = %{ + "nekomimi_girl_emoji_007" => + "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png" + } + + assert %{ + "object" => %{ + "tag" => ^expected_tags, + "emoji" => ^expected_emoji, + "formerRepresentations" => %{"orderedItems" => [item]} + } + } = filtered + + assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item + end + + test "processes updates" do + {:ok, filtered} = + MRF.filter_one(EmojiPolicy, @status_data_with_history |> Map.put("type", "Update")) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + expected_emoji = %{ + "nekomimi_girl_emoji_007" => + "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png" + } + + assert %{ + "object" => %{ + "tag" => ^expected_tags, + "emoji" => ^expected_emoji, + "formerRepresentations" => %{"orderedItems" => [item]} + } + } = filtered + + assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item + end end describe "remove_shortcode" do @@ -152,5 +216,117 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do assert %{"object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji}} = filtered end + + test "processes status with history" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data_with_history) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + expected_emoji = %{ + "nekomimi_girl_emoji_007" => + "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png" + } + + assert %{ + "object" => %{ + "tag" => ^expected_tags, + "emoji" => ^expected_emoji, + "formerRepresentations" => %{"orderedItems" => [item]} + } + } = filtered + + assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item + end + + test "processes updates" do + {:ok, filtered} = + MRF.filter_one(EmojiPolicy, @status_data_with_history |> Map.put("type", "Update")) + + expected_tags = [@emoji_tags |> Enum.at(2)] ++ @misc_tags + + expected_emoji = %{ + "nekomimi_girl_emoji_007" => + "https://example.org/emoji/nekomimi_girl_emoji/nekomimi_girl_emoji_007.png" + } + + assert %{ + "object" => %{ + "tag" => ^expected_tags, + "emoji" => ^expected_emoji, + "formerRepresentations" => %{"orderedItems" => [item]} + } + } = filtered + + assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item + end + end + + describe "federated_timeline_removal_url" do + setup do + clear_config([:mrf_emoji, :federated_timeline_removal_url], [ + "https://example.org/test.png", + ~r{/biribiri/mikoto_smile[23]\.png}, + "nekomimi_girl_emoji" + ]) + + :ok + end + + test "processes status" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data) + + expected_tags = @status_data["object"]["tag"] + expected_emoji = @status_data["object"]["emoji"] + + expected_to = ["https://example.org/self"] + expected_cc = [Pleroma.Constants.as_public(), "https://example.org/someone"] + + assert %{ + "to" => ^expected_to, + "cc" => ^expected_cc, + "object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} + } = filtered + end + + test "ignore updates" do + {:ok, filtered} = MRF.filter_one(EmojiPolicy, @status_data |> Map.put("type", "Update")) + + expected_tags = @status_data["object"]["tag"] + expected_emoji = @status_data["object"]["emoji"] + + expected_to = ["https://example.org/self", Pleroma.Constants.as_public()] + expected_cc = ["https://example.org/someone"] + + assert %{ + "to" => ^expected_to, + "cc" => ^expected_cc, + "object" => %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} + } = filtered + end + + test "processes status with history" do + status = + @status_data_with_history + |> put_in(["object", "tag"], @misc_tags) + |> put_in(["object", "emoji"], %{}) + + {:ok, filtered} = MRF.filter_one(EmojiPolicy, status) + + expected_tags = @status_data["object"]["tag"] + expected_emoji = @status_data["object"]["emoji"] + + expected_to = ["https://example.org/self"] + expected_cc = [Pleroma.Constants.as_public(), "https://example.org/someone"] + + assert %{ + "to" => ^expected_to, + "cc" => ^expected_cc, + "object" => %{ + "formerRepresentations" => %{ + "orderedItems" => [%{"tag" => ^expected_tags, "emoji" => ^expected_emoji}] + } + } + } = filtered + end end end From f50422c3802cd2316cfa6312d5a88c018c722843 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 28 Feb 2023 11:48:34 -0500 Subject: [PATCH 0284/1008] Move emoji_policy.ex to the right place --- lib/pleroma/web/activity_pub/{ => mrf}/emoji_policy.ex | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/pleroma/web/activity_pub/{ => mrf}/emoji_policy.ex (100%) diff --git a/lib/pleroma/web/activity_pub/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex similarity index 100% rename from lib/pleroma/web/activity_pub/emoji_policy.ex rename to lib/pleroma/web/activity_pub/mrf/emoji_policy.ex From 18a8378beb01a7ee93afcddf16e2699c5fe6d35a Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 28 Feb 2023 12:05:54 -0500 Subject: [PATCH 0285/1008] Update config cheatsheet --- docs/configuration/cheatsheet.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 1e49a79d0..696d10e74 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -261,6 +261,11 @@ Notes: * `follower_nickname`: The name of the bot account to use for following newly discovered users. Using `followbot` or similar is strongly suggested. +#### :mrf_emoji +* `remove_url`: A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to both statuses and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). +* `remove_shortcode`: A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to both statuses and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). +* `federated_timeline_removal_url`: A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). +* `federated_timeline_removal_shortcode`: A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). ### :activitypub * `unfollow_blocked`: Whether blocks result in people getting unfollowed From 20d193c91da587372c618c53020a1f90eff20a7b Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 28 Feb 2023 12:14:48 -0500 Subject: [PATCH 0286/1008] Improve config examples for EmojiPolicy --- lib/pleroma/web/activity_pub/mrf/emoji_policy.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex index df1566ec3..9a2e8eade 100644 --- a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex @@ -231,7 +231,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, - suggestions: ["foo", ~r/foo/iu] + suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu] }, %{ key: :remove_shortcode, @@ -251,7 +251,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, - suggestions: ["foo", ~r/foo/iu] + suggestions: ["https://example.org/foo.png", ~r/example.org\/foo/iu] }, %{ key: :federated_timeline_removal_shortcode, From ef8a6c539a98633d1a78fa42379952b93f847309 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 13 Jun 2023 14:53:20 -0400 Subject: [PATCH 0287/1008] Make EmojiPolicy aware of custom emoji reactions --- docs/configuration/cheatsheet.md | 4 +- .../web/activity_pub/mrf/emoji_policy.ex | 33 +++++++++---- .../activity_pub/mrf/emoji_policy_test.exs | 46 +++++++++++++++++++ 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 696d10e74..f43cde114 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -262,8 +262,8 @@ Notes: * `follower_nickname`: The name of the bot account to use for following newly discovered users. Using `followbot` or similar is strongly suggested. #### :mrf_emoji -* `remove_url`: A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to both statuses and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). -* `remove_shortcode`: A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to both statuses and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). +* `remove_url`: A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). +* `remove_shortcode`: A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). * `federated_timeline_removal_url`: A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). * `federated_timeline_removal_shortcode`: A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). diff --git a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex index 9a2e8eade..13201ac55 100644 --- a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex @@ -55,6 +55,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do end end + @impl Pleroma.Web.ActivityPub.MRF.Policy + def filter(%{"type" => "EmojiReact"} = object) do + with {:ok, _} <- + matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do + {:ok, object} + else + _ -> + {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} + end + end + @impl Pleroma.Web.ActivityPub.MRF.Policy def filter(message) do {:ok, message} @@ -133,20 +144,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do end end - defp maybe_delist(%{"object" => object, "to" => to, "type" => "Create"} = activity) do - check = fn object -> - if any_emoji_match?(object, &url_from_tag/1, &url_from_emoji/1, config_unlist_url()) or + defp matched_emoji_checker(urls, shortcodes) do + fn object -> + if any_emoji_match?(object, &url_from_tag/1, &url_from_emoji/1, urls) or any_emoji_match?( object, &shortcode_from_tag/1, &shortcode_from_emoji/1, - config_unlist_shortcode() + shortcodes ) do - {:should_delist, nil} + {:matched, nil} else {:ok, %{}} end end + end + + defp maybe_delist(%{"object" => object, "to" => to, "type" => "Create"} = activity) do + check = matched_emoji_checker(config_unlist_url(), config_unlist_shortcode()) should_delist? = fn object -> with {:ok, _} <- Pleroma.Object.Updater.do_with_history(object, check) do @@ -173,7 +188,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do defp any_emoji_match?(object, extract_from_tag, extract_from_emoji, patterns) do Kernel.||( Enum.any?( - object["tag"], + object["tag"] || [], fn %{"type" => "Emoji"} = tag -> str = extract_from_tag.(tag) @@ -188,7 +203,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do false end ), - object["emoji"] + (object["emoji"] || []) |> Enum.any?(fn emoji -> match_any?(extract_from_emoji.(emoji), patterns) end) ) end @@ -227,7 +242,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do key: :remove_url, type: {:list, :string}, description: """ - A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to both statuses and user profiles. + A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, @@ -237,7 +252,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do key: :remove_shortcode, type: {:list, :string}, description: """ - A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to both statuses and user profiles. + A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, diff --git a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs index 1e1b5c578..ae4c44f1c 100644 --- a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs @@ -109,6 +109,30 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do "cc" => ["https://example.org/someone"] } + @emoji_react_data %{ + "type" => "EmojiReact", + "tag" => [@emoji_tags |> Enum.at(3)], + "object" => "https://example.org/someobject", + "to" => ["https://example.org/self"], + "cc" => ["https://example.org/someone"] + } + + @emoji_react_data_matching_regex %{ + "type" => "EmojiReact", + "tag" => [@emoji_tags |> Enum.at(1)], + "object" => "https://example.org/someobject", + "to" => ["https://example.org/self"], + "cc" => ["https://example.org/someone"] + } + + @emoji_react_data_matching_nothing %{ + "type" => "EmojiReact", + "tag" => [@emoji_tags |> Enum.at(2)], + "object" => "https://example.org/someobject", + "to" => ["https://example.org/self"], + "cc" => ["https://example.org/someone"] + } + describe "remove_url" do setup do clear_config([:mrf_emoji, :remove_url], [ @@ -183,6 +207,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item end + + test "processes EmojiReact" do + assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} == + MRF.filter_one(EmojiPolicy, @emoji_react_data) + + assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} == + MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_regex) + + assert {:ok, @emoji_react_data_matching_nothing} == + MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_nothing) + end end describe "remove_shortcode" do @@ -259,6 +294,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do assert %{"tag" => ^expected_tags, "emoji" => ^expected_emoji} = item end + + test "processes EmojiReact" do + assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} == + MRF.filter_one(EmojiPolicy, @emoji_react_data) + + assert {:reject, "[EmojiPolicy] Rejected for having disallowed emoji"} == + MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_regex) + + assert {:ok, @emoji_react_data_matching_nothing} == + MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_nothing) + end end describe "federated_timeline_removal_url" do From d670dbdbd3e675b7d29c97e8bfd5301dcf3c9de0 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 13 Jun 2023 14:55:27 -0400 Subject: [PATCH 0288/1008] Test that unicode emoji reactions are not affected --- .../web/activity_pub/mrf/emoji_policy_test.exs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs index ae4c44f1c..121ee1ea0 100644 --- a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs @@ -133,6 +133,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do "cc" => ["https://example.org/someone"] } + @emoji_react_data_unicode %{ + "type" => "EmojiReact", + "content" => "😍", + "object" => "https://example.org/someobject", + "to" => ["https://example.org/self"], + "cc" => ["https://example.org/someone"] + } + describe "remove_url" do setup do clear_config([:mrf_emoji, :remove_url], [ @@ -217,6 +225,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do assert {:ok, @emoji_react_data_matching_nothing} == MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_nothing) + + assert {:ok, @emoji_react_data_unicode} == + MRF.filter_one(EmojiPolicy, @emoji_react_data_unicode) end end @@ -304,6 +315,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do assert {:ok, @emoji_react_data_matching_nothing} == MRF.filter_one(EmojiPolicy, @emoji_react_data_matching_nothing) + + assert {:ok, @emoji_react_data_unicode} == + MRF.filter_one(EmojiPolicy, @emoji_react_data_unicode) end end From 0d914e17bedb49a5efb8c0268d4a6df2e7ffdf96 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 13 Jun 2023 14:56:16 -0400 Subject: [PATCH 0289/1008] Add changelog --- changelog.d/emoji-policy.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/emoji-policy.add diff --git a/changelog.d/emoji-policy.add b/changelog.d/emoji-policy.add new file mode 100644 index 000000000..45510c4f6 --- /dev/null +++ b/changelog.d/emoji-policy.add @@ -0,0 +1 @@ +Implement MRF policy to reject or delist according to emojis From ba3aa4f86da77ece8ac472c40da180ab1921c304 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 20 Jun 2023 10:14:01 -0400 Subject: [PATCH 0290/1008] Fix edge cases --- lib/pleroma/constants.ex | 12 +++ .../web/activity_pub/mrf/emoji_policy.ex | 79 ++++++++++--------- .../activity_pub/mrf/emoji_policy_test.exs | 33 ++++++++ 3 files changed, 87 insertions(+), 37 deletions(-) diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index cfb405218..7b4fd03b6 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -42,6 +42,18 @@ defmodule Pleroma.Constants do ] ) + const(status_object_types, + do: [ + "Note", + "Question", + "Audio", + "Video", + "Event", + "Article", + "Page" + ] + ) + const(updatable_object_types, do: [ "Note", diff --git a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex index 13201ac55..95d6a6d43 100644 --- a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex @@ -31,8 +31,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do def history_awareness, do: :manual @impl Pleroma.Web.ActivityPub.MRF.Policy - def filter(%{"type" => type, "object" => %{} = object} = message) - when type in ["Create", "Update"] do + def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = message) + when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do with {:ok, object} <- Updater.do_with_history(object, fn object -> {:ok, process_remove(object, :url, config_remove_url())} @@ -102,46 +102,51 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do end defp process_remove_impl(object, extract_from_tag, extract_from_emoji, patterns) do - processed_tag = - Enum.filter( - object["tag"], - fn - %{"type" => "Emoji"} = tag -> - str = extract_from_tag.(tag) + object = + if object["tag"] do + Map.put( + object, + "tag", + Enum.filter( + object["tag"], + fn + %{"type" => "Emoji"} = tag -> + str = extract_from_tag.(tag) - if is_binary(str) do - not match_any?(str, patterns) - else - true + if is_binary(str) do + not match_any?(str, patterns) + else + true + end + + _ -> + true end - - _ -> - true - end - ) - - processed_emoji = - if object["emoji"] do - object["emoji"] - |> Enum.reduce(%{}, fn {name, url} = emoji, acc -> - if not match_any?(extract_from_emoji.(emoji), patterns) do - Map.put(acc, name, url) - else - acc - end - end) + ) + ) else - nil + object end - if processed_emoji do - object - |> Map.put("tag", processed_tag) - |> Map.put("emoji", processed_emoji) - else - object - |> Map.put("tag", processed_tag) - end + object = + if object["emoji"] do + Map.put( + object, + "emoji", + object["emoji"] + |> Enum.reduce(%{}, fn {name, url} = emoji, acc -> + if not match_any?(extract_from_emoji.(emoji), patterns) do + Map.put(acc, name, url) + else + acc + end + end) + ) + else + object + end + + object end defp matched_emoji_checker(urls, shortcodes) do diff --git a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs index 121ee1ea0..7350800f0 100644 --- a/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/emoji_policy_test.exs @@ -389,4 +389,37 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicyTest do } = filtered end end + + describe "edge cases" do + setup do + clear_config([:mrf_emoji, :remove_url], [ + "https://example.org/test.png", + ~r{/biribiri/mikoto_smile[23]\.png}, + "nekomimi_girl_emoji" + ]) + + :ok + end + + test "non-statuses" do + answer = @status_data |> put_in(["object", "type"], "Answer") + {:ok, filtered} = MRF.filter_one(EmojiPolicy, answer) + + assert filtered == answer + end + + test "without tag" do + status = @status_data |> Map.put("object", Map.drop(@status_data["object"], ["tag"])) + {:ok, filtered} = MRF.filter_one(EmojiPolicy, status) + + refute Map.has_key?(filtered["object"], "tag") + end + + test "without emoji" do + status = @status_data |> Map.put("object", Map.drop(@status_data["object"], ["emoji"])) + {:ok, filtered} = MRF.filter_one(EmojiPolicy, status) + + refute Map.has_key?(filtered["object"], "emoji") + end + end end From 1459d64508b10725231d8f4256fbc68166c1b8ae Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 7 Jul 2023 07:09:35 -0400 Subject: [PATCH 0291/1008] Make regex-to-string descriptor reusable --- .../web/activity_pub/mrf/emoji_policy.ex | 12 ++---------- .../web/activity_pub/mrf/keyword_policy.ex | 16 ++++------------ lib/pleroma/web/activity_pub/mrf/utils.ex | 15 +++++++++++++++ .../web/activity_pub/mrf/utils_test.exs | 19 +++++++++++++++++++ 4 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/mrf/utils.ex create mode 100644 test/pleroma/web/activity_pub/mrf/utils_test.exs diff --git a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex index 95d6a6d43..f884962b9 100644 --- a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do require Pleroma.Constants alias Pleroma.Object.Updater + alias Pleroma.Web.ActivityPub.MRF.Utils @moduledoc "Reject or force-unlisted emojis with certain URLs or names" @@ -215,19 +216,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do @impl Pleroma.Web.ActivityPub.MRF.Policy def describe do - # This horror is needed to convert regex sigils to strings mrf_emoji = Pleroma.Config.get(:mrf_emoji, []) |> Enum.map(fn {key, value} -> - {key, - Enum.map(value, fn - pattern -> - if not is_binary(pattern) do - inspect(pattern) - else - pattern - end - end)} + {key, Enum.map(value, &Utils.describe_regex_or_string/1)} end) |> Enum.into(%{}) diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index 687ec6c2f..874fe9ab9 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do require Pleroma.Constants + alias Pleroma.Web.ActivityPub.MRF.Utils + @moduledoc "Reject or Word-Replace messages with a keyword or regex" @behaviour Pleroma.Web.ActivityPub.MRF.Policy @@ -128,7 +130,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do @impl true def describe do - # This horror is needed to convert regex sigils to strings mrf_keyword = Pleroma.Config.get(:mrf_keyword, []) |> Enum.map(fn {key, value} -> @@ -136,21 +137,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do Enum.map(value, fn {pattern, replacement} -> %{ - "pattern" => - if not is_binary(pattern) do - inspect(pattern) - else - pattern - end, + "pattern" => Utils.describe_regex_or_string(pattern), "replacement" => replacement } pattern -> - if not is_binary(pattern) do - inspect(pattern) - else - pattern - end + Utils.describe_regex_or_string(pattern) end)} end) |> Enum.into(%{}) diff --git a/lib/pleroma/web/activity_pub/mrf/utils.ex b/lib/pleroma/web/activity_pub/mrf/utils.ex new file mode 100644 index 000000000..f2dc9eea9 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/utils.ex @@ -0,0 +1,15 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.Utils do + @spec describe_regex_or_string(String.t() | Regex.t()) :: String.t() + def describe_regex_or_string(pattern) do + # This horror is needed to convert regex sigils to strings + if not is_binary(pattern) do + inspect(pattern) + else + pattern + end + end +end diff --git a/test/pleroma/web/activity_pub/mrf/utils_test.exs b/test/pleroma/web/activity_pub/mrf/utils_test.exs new file mode 100644 index 000000000..3bbc2cfd3 --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/utils_test.exs @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.UtilsTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Web.ActivityPub.MRF.Utils + + describe "describe_regex_or_string/1" do + test "describes regex" do + assert "~r/foo/i" == Utils.describe_regex_or_string(~r/foo/i) + end + + test "returns string as-is" do + assert "foo" == Utils.describe_regex_or_string("foo") + end + end +end From ea4225a646b355150fb8e5e8c77d7fdc58b5e7ef Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 18 Jul 2023 18:39:59 -0400 Subject: [PATCH 0292/1008] Restrict attachments to only uploaded files only --- changelog.d/attachment-type-check.fix | 1 + lib/pleroma/constants.ex | 2 ++ lib/pleroma/web/common_api/utils.ex | 7 ++++++- test/pleroma/web/common_api/utils_test.exs | 11 ++++++++--- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 changelog.d/attachment-type-check.fix diff --git a/changelog.d/attachment-type-check.fix b/changelog.d/attachment-type-check.fix new file mode 100644 index 000000000..9e14b75f1 --- /dev/null +++ b/changelog.d/attachment-type-check.fix @@ -0,0 +1 @@ +Restrict attachments to only uploaded files only diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 7b4fd03b6..6befc6897 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -81,4 +81,6 @@ defmodule Pleroma.Constants do const(mime_regex, do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/ ) + + const(upload_object_types, do: ["Document", "Image"]) end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index a93c97e1e..b9fe0224c 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -59,7 +59,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do end defp get_attachment(media_id) do - Repo.get(Object, media_id) + with %Object{data: data} = object <- Repo.get(Object, media_id), + %{"type" => type} when type in Pleroma.Constants.upload_object_types() <- data do + object + else + _ -> nil + end end @spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())} diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index d309c6ded..ca5b92683 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -592,7 +592,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do end test "returns list attachments with desc" do - object = insert(:note) + object = insert(:attachment) desc = Jason.encode!(%{object.id => "test-desc"}) assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [ @@ -603,7 +603,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do describe "attachments_from_ids/1" do test "returns attachments with descs" do - object = insert(:note) + object = insert(:attachment) desc = Jason.encode!(%{object.id => "test-desc"}) assert Utils.attachments_from_ids(%{ @@ -615,13 +615,18 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do end test "returns attachments without descs" do - object = insert(:note) + object = insert(:attachment) assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [object.data] end test "returns [] when not pass media_ids" do assert Utils.attachments_from_ids(%{}) == [] end + + test "checks that the object is of upload type" do + object = insert(:note) + assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [] + end end describe "maybe_add_list_data/3" do From dc4de79d43ba941d8aa16f7c14887faae9f65e9f Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Wed, 7 Dec 2022 22:37:50 +0500 Subject: [PATCH 0293/1008] status context: perform visibility check on activities around a status issue #2927 --- lib/pleroma/web/activity_pub/activity_pub.ex | 23 ++ .../controllers/status_controller_test.exs | 267 ++++++++++++++++++ 2 files changed, 290 insertions(+) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c93288b79..f1a12d22d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -455,6 +455,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> maybe_preload_objects(opts) |> maybe_preload_bookmarks(opts) |> maybe_set_thread_muted_field(opts) + |> restrict_unauthenticated(opts[:user]) |> restrict_blocked(opts) |> restrict_blockers_visibility(opts) |> restrict_recipients(recipients, opts[:user]) @@ -1215,6 +1216,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_filtered(query, _), do: query + defp restrict_unauthenticated(query, nil) do + local = Config.restrict_unauthenticated_access?(:activities, :local) + remote = Config.restrict_unauthenticated_access?(:activities, :remote) + + cond do + local and remote -> + # is there a better way to handle this? + from(activity in query, where: 1 == 0) + + local -> + from(activity in query, where: activity.local == false) + + remote -> + from(activity in query, where: activity.local == true) + + true -> + query + end + end + + defp restrict_unauthenticated(query, _), do: query + defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query defp exclude_poll_votes(query, _) do diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 4f434cb69..76c289ee7 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -771,6 +771,49 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do {:ok, local: local, remote: remote} end + defp local_and_remote_context_activities do + local_user_1 = insert(:user) + local_user_2 = insert(:user) + remote_user = insert(:user, local: false) + + {:ok, %{id: id1, data: %{"context" => context}}} = + CommonAPI.post(local_user_1, %{status: "post"}) + + {:ok, %{id: id2} = post} = + CommonAPI.post(local_user_2, %{status: "local reply", in_reply_to_status_id: id1}) + + params = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "actor" => remote_user.ap_id, + "type" => "Create", + "context" => context, + "id" => "#{remote_user.ap_id}/activities/1", + "inReplyTo" => post.data["id"], + "object" => %{ + "type" => "Note", + "content" => "remote reply", + "context" => context, + "id" => "#{remote_user.ap_id}/objects/1", + "attributedTo" => remote_user.ap_id, + "to" => [ + local_user_1.ap_id, + local_user_2.ap_id, + "https://www.w3.org/ns/activitystreams#Public" + ] + }, + "to" => [ + local_user_1.ap_id, + local_user_2.ap_id, + "https://www.w3.org/ns/activitystreams#Public" + ] + } + + {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params) + {:ok, remote_activity} = ObanHelpers.perform(job) + + %{locals: [id1, id2], remote: remote_activity.id, context: context} + end + describe "status with restrict unauthenticated activities for local and remote" do setup do: local_and_remote_activities() @@ -957,6 +1000,230 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do end end + describe "getting status contexts restricted unauthenticated for local and remote" do + setup do: local_and_remote_context_activities() + + setup do: clear_config([:restrict_unauthenticated, :activities, :local], true) + + setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) + + test "if user is unauthenticated", %{conn: conn, locals: [post_id, _]} do + res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") + + assert json_response_and_validate_schema(res_conn, 200) == %{ + "ancestors" => [], + "descendants" => [] + } + end + + test "if user is unauthenticated reply", %{conn: conn, locals: [_, reply_id]} do + res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context") + + assert json_response_and_validate_schema(res_conn, 200) == %{ + "ancestors" => [], + "descendants" => [] + } + end + + test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") + + %{"ancestors" => [], "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert reply_id in descendant_ids + assert remote_reply_id in descendant_ids + end + + test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context") + + %{"ancestors" => ancestors, "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids = + ancestors + |> Enum.map(& &1["id"]) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert post_id in ancestor_ids + assert remote_reply_id in descendant_ids + end + end + + describe "getting status contexts restricted unauthenticated for local" do + setup do: local_and_remote_context_activities() + + setup do: clear_config([:restrict_unauthenticated, :activities, :local], true) + + setup do: clear_config([:restrict_unauthenticated, :activities, :remote], false) + + test "if user is unauthenticated", %{ + conn: conn, + locals: [post_id, reply_id], + remote: remote_reply_id + } do + res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") + + %{"ancestors" => [], "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert reply_id not in descendant_ids + assert remote_reply_id in descendant_ids + end + + test "if user is unauthenticated reply", %{ + conn: conn, + locals: [post_id, reply_id], + remote: remote_reply_id + } do + res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context") + + %{"ancestors" => ancestors, "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids = + ancestors + |> Enum.map(& &1["id"]) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert post_id not in ancestor_ids + assert remote_reply_id in descendant_ids + end + + test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") + + %{"ancestors" => [], "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert reply_id in descendant_ids + assert remote_reply_id in descendant_ids + end + + test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context") + + %{"ancestors" => ancestors, "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids = + ancestors + |> Enum.map(& &1["id"]) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert post_id in ancestor_ids + assert remote_reply_id in descendant_ids + end + end + + describe "getting status contexts restricted unauthenticated for remote" do + setup do: local_and_remote_context_activities() + + setup do: clear_config([:restrict_unauthenticated, :activities, :local], false) + + setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) + + test "if user is unauthenticated", %{ + conn: conn, + locals: [post_id, reply_id], + remote: remote_reply_id + } do + res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") + + %{"ancestors" => [], "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert reply_id in descendant_ids + assert remote_reply_id not in descendant_ids + end + + test "if user is unauthenticated reply", %{ + conn: conn, + locals: [post_id, reply_id], + remote: remote_reply_id + } do + res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context") + + %{"ancestors" => ancestors, "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids = + ancestors + |> Enum.map(& &1["id"]) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert post_id in ancestor_ids + assert remote_reply_id not in descendant_ids + end + + test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") + + %{"ancestors" => [], "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + reply_ids = + descendants + |> Enum.map(& &1["id"]) + + assert reply_id in reply_ids + assert remote_reply_id in reply_ids + end + + test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context") + + %{"ancestors" => ancestors, "descendants" => descendants} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids = + ancestors + |> Enum.map(& &1["id"]) + + descendant_ids = + descendants + |> Enum.map(& &1["id"]) + + assert post_id in ancestor_ids + assert remote_reply_id in descendant_ids + end + end + describe "deleting a status" do test "when you created it" do %{user: author, conn: conn} = oauth_access(["write:statuses"]) From e5e76ec44559af62eb56043add0489c61e6c1ee5 Mon Sep 17 00:00:00 2001 From: Faried Nawaz Date: Fri, 10 Feb 2023 01:32:32 +0500 Subject: [PATCH 0294/1008] cleaner ecto query to handle restrict_unauthenticated for activities This fix is for this case: config :pleroma, :restrict_unauthenticated, activities: %{local: true, remote: true} --- lib/pleroma/web/activity_pub/activity_pub.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index f1a12d22d..3979d418e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1222,8 +1222,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do cond do local and remote -> - # is there a better way to handle this? - from(activity in query, where: 1 == 0) + from(activity in query, where: false) local -> from(activity in query, where: activity.local == false) From 11ce81d4af6b428fabb9d4c6f0098d786a21487b Mon Sep 17 00:00:00 2001 From: faried nawaz Date: Fri, 28 Jul 2023 18:49:05 +0500 Subject: [PATCH 0295/1008] add changelog entry --- changelog.d/3801.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3801.fix diff --git a/changelog.d/3801.fix b/changelog.d/3801.fix new file mode 100644 index 000000000..8c2ec0199 --- /dev/null +++ b/changelog.d/3801.fix @@ -0,0 +1 @@ +Filter context activities using Visibility.visible_for_user? From 18a0c923d0da4c8fb6e33b383dabd1d06bb22968 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 3 Aug 2023 13:08:37 -0400 Subject: [PATCH 0296/1008] Resolve information disclosure vulnerability through emoji pack archive download endpoint The pack name has been sanitized so an attacker cannot upload a media file called pack.json with their own handcrafted list of emoji files as arbitrary files on the filesystem and then call the emoji pack archive download endpoint with a pack name crafted to the location of the media file they uploaded which tricks Pleroma into generating a zip file of the target files the attacker wants to download. The attack only works if the Pleroma instance does not have the AnonymizeFilename upload filter enabled, which is currently the default. Reported by: graf@poast.org --- changelog.d/emoji-pack-sanitization.security | 1 + lib/pleroma/emoji/pack.ex | 1 + test/pleroma/emoji/pack_test.exs | 4 ++++ 3 files changed, 6 insertions(+) create mode 100644 changelog.d/emoji-pack-sanitization.security diff --git a/changelog.d/emoji-pack-sanitization.security b/changelog.d/emoji-pack-sanitization.security new file mode 100644 index 000000000..f3218abd4 --- /dev/null +++ b/changelog.d/emoji-pack-sanitization.security @@ -0,0 +1 @@ +Emoji pack loader sanitizes pack names diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index a361ea200..6e58f8898 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -285,6 +285,7 @@ defmodule Pleroma.Emoji.Pack do @spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()} def load_pack(name) do + name = Path.basename(name) pack_file = Path.join([emoji_path(), name, "pack.json"]) with {:ok, _} <- File.stat(pack_file), diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs index 18b99da75..00001abfc 100644 --- a/test/pleroma/emoji/pack_test.exs +++ b/test/pleroma/emoji/pack_test.exs @@ -90,4 +90,8 @@ defmodule Pleroma.Emoji.PackTest do assert updated_pack.files_count == 1 end + + test "load_pack/1 ignores path traversal in a forged pack name", %{pack: pack} do + assert {:ok, ^pack} = Pack.load_pack("../../../../../dump_pack") + end end From 2c795094535537a8607cc0d3b7f076a609636f40 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 3 Aug 2023 13:08:37 -0400 Subject: [PATCH 0297/1008] Resolve information disclosure vulnerability through emoji pack archive download endpoint The pack name has been sanitized so an attacker cannot upload a media file called pack.json with their own handcrafted list of emoji files as arbitrary files on the filesystem and then call the emoji pack archive download endpoint with a pack name crafted to the location of the media file they uploaded which tricks Pleroma into generating a zip file of the target files the attacker wants to download. The attack only works if the Pleroma instance does not have the AnonymizeFilename upload filter enabled, which is currently the default. Reported by: graf@poast.org --- changelog.d/emoji-pack-sanitization.security | 1 + lib/pleroma/emoji/pack.ex | 1 + test/pleroma/emoji/pack_test.exs | 4 ++++ 3 files changed, 6 insertions(+) create mode 100644 changelog.d/emoji-pack-sanitization.security diff --git a/changelog.d/emoji-pack-sanitization.security b/changelog.d/emoji-pack-sanitization.security new file mode 100644 index 000000000..f3218abd4 --- /dev/null +++ b/changelog.d/emoji-pack-sanitization.security @@ -0,0 +1 @@ +Emoji pack loader sanitizes pack names diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index a361ea200..6e58f8898 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -285,6 +285,7 @@ defmodule Pleroma.Emoji.Pack do @spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()} def load_pack(name) do + name = Path.basename(name) pack_file = Path.join([emoji_path(), name, "pack.json"]) with {:ok, _} <- File.stat(pack_file), diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs index 18b99da75..00001abfc 100644 --- a/test/pleroma/emoji/pack_test.exs +++ b/test/pleroma/emoji/pack_test.exs @@ -90,4 +90,8 @@ defmodule Pleroma.Emoji.PackTest do assert updated_pack.files_count == 1 end + + test "load_pack/1 ignores path traversal in a forged pack name", %{pack: pack} do + assert {:ok, ^pack} = Pack.load_pack("../../../../../dump_pack") + end end From 4befb3b1d02f32eb2c56f12e4684a7bb3167b0ee Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 22 Jun 2023 00:46:52 +0200 Subject: [PATCH 0298/1008] Config: Restrict permissions of OTP config file --- lib/pleroma/config/release_runtime_provider.ex | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex index 91e5f1a54..9ec0f975e 100644 --- a/lib/pleroma/config/release_runtime_provider.ex +++ b/lib/pleroma/config/release_runtime_provider.ex @@ -20,6 +20,20 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do with_runtime_config = if File.exists?(config_path) do + # + %File.Stat{mode: mode} = File.lstat!(config_path) + + if Bitwise.band(mode, 0o007) > 0 do + raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}" + end + + if Bitwise.band(mode, 0o020) > 0 do + raise "Configuration at #{config_path} has group-wise write permissions, execute the following: chmod g-w #{config_path}" + end + + # Note: Elixir doesn't provides a getuid(2) + # so cannot forbid group-read only when config is owned by us + runtime_config = Config.Reader.read!(config_path) with_defaults From bd7381f2f4139c26d9dbb8aad77ce77be7777532 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 22 Jun 2023 00:58:05 +0200 Subject: [PATCH 0299/1008] instance gen: Reduce permissions of pleroma directories and config files --- lib/mix/tasks/pleroma/instance.ex | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 5c93f19ff..5d8b254a2 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -266,12 +266,20 @@ defmodule Mix.Tasks.Pleroma.Instance do config_dir = Path.dirname(config_path) psql_dir = Path.dirname(psql_path) + # Note: Distros requiring group read (0o750) on those directories should + # pre-create the directories. [config_dir, psql_dir, static_dir, uploads_dir] |> Enum.reject(&File.exists?/1) - |> Enum.map(&File.mkdir_p!/1) + |> Enum.each(fn dir -> + File.mkdir_p!(dir) + File.chmod!(dir, 0o700) + end) shell_info("Writing config to #{config_path}.") + # Sadly no fchmod(2) equivalent in Elixir… + File.touch!(config_path) + File.chmod!(config_path, 0o640) File.write(config_path, result_config) shell_info("Writing the postgres script to #{psql_path}.") File.write(psql_path, result_psql) @@ -290,8 +298,7 @@ defmodule Mix.Tasks.Pleroma.Instance do else shell_error( "The task would have overwritten the following files:\n" <> - (Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <> - "Rerun with `--force` to overwrite them." + Enum.map_join(will_overwrite, &"- #{&1}\n") <> "Rerun with `--force` to overwrite them." ) end end From 22df32b3f5cfe9fe0a4a97ff799df72c091b676e Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 22 Jun 2023 01:00:25 +0200 Subject: [PATCH 0300/1008] changelog: Entry for config permissions restrictions Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3135 --- changelog.d/otp_perms.security | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/otp_perms.security diff --git a/changelog.d/otp_perms.security b/changelog.d/otp_perms.security new file mode 100644 index 000000000..a3da1c677 --- /dev/null +++ b/changelog.d/otp_perms.security @@ -0,0 +1 @@ +- Reduced permissions of config files and directories, distros requiring greater permissions like group-read need to pre-create the directories \ No newline at end of file From 76e408e42d1da123a955b85490f05f6d810172f9 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Aug 2023 07:16:50 +0200 Subject: [PATCH 0301/1008] release_runtime_provider_test: chmod config for hardened permissions Git doesn't manages file permissions precisely enough for us. --- test/pleroma/config/release_runtime_provider_test.exs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs index 4e0d4c838..8ff578e63 100644 --- a/test/pleroma/config/release_runtime_provider_test.exs +++ b/test/pleroma/config/release_runtime_provider_test.exs @@ -17,6 +17,8 @@ defmodule Pleroma.Config.ReleaseRuntimeProviderTest do end test "merged runtime config" do + assert :ok == File.chmod!("test/fixtures/config/temp.secret.exs", 0o640) + merged = ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs") @@ -25,6 +27,8 @@ defmodule Pleroma.Config.ReleaseRuntimeProviderTest do end test "merged exported config" do + assert :ok == File.chmod!("test/fixtures/config/temp.exported_from_db.secret.exs", 0o640) + ExUnit.CaptureIO.capture_io(fn -> merged = ReleaseRuntimeProvider.load([], @@ -37,6 +41,9 @@ defmodule Pleroma.Config.ReleaseRuntimeProviderTest do end test "runtime config is merged with exported config" do + assert :ok == File.chmod!("test/fixtures/config/temp.secret.exs", 0o640) + assert :ok == File.chmod!("test/fixtures/config/temp.exported_from_db.secret.exs", 0o640) + merged = ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs", From c37561214a803f9011d5ec6af8b8c07e547c19ff Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Aug 2023 06:49:19 +0200 Subject: [PATCH 0302/1008] Force the use of amd64 runners for jobs using ci-base --- .gitlab-ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8b0381d11..91e568a32 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,7 +32,13 @@ before_script: after_script: - rm -rf _build/*/lib/pleroma +.using-ci-base: + tags: + - amd64 + build: + extends: + - .using-ci-base stage: build only: changes: &build_changes_policy @@ -44,6 +50,8 @@ build: - mix compile --force spec-build: + extends: + - .using-ci-base stage: test only: changes: @@ -57,6 +65,8 @@ spec-build: - mix pleroma.openapi_spec spec.json benchmark: + extends: + - .using-ci-base stage: benchmark when: manual variables: @@ -71,6 +81,8 @@ benchmark: - mix pleroma.load_testing unit-testing: + extends: + - .using-ci-base stage: test only: changes: *build_changes_policy @@ -94,6 +106,8 @@ unit-testing: path: coverage.xml unit-testing-erratic: + extends: + - .using-ci-base stage: test retry: 2 allow_failure: true @@ -129,6 +143,8 @@ unit-testing-erratic: # - mix test --trace --only federated unit-testing-rum: + extends: + - .using-ci-base stage: test only: changes: *build_changes_policy @@ -162,6 +178,8 @@ lint: - mix format --check-formatted analysis: + extends: + - .using-ci-base stage: test only: changes: *build_changes_policy From 5ac2b7417d052a493b38ee05b393ae7c78e89484 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Aug 2023 09:16:08 +0200 Subject: [PATCH 0303/1008] test: Fix warnings --- .../activity_pub/transmogrifier/emoji_react_handling_test.exs | 2 +- test/pleroma/web/mastodon_api/update_credentials_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs index 9d99df27c..83bf59c6f 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -65,7 +65,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do object = Object.get_by_ap_id(data["object"]) assert object.data["reaction_count"] == 1 - assert match?([[emoji, _]], object.data["reactions"]) + assert match?([[^emoji, _]], object.data["reactions"]) end test "it reject invalid emoji reactions" do diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index 57fa0f047..40f79d103 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -375,7 +375,7 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do "pleroma_background_image" => new_background_oversized }) - assert user_response = json_response_and_validate_schema(res, 413) + assert _user_response = json_response_and_validate_schema(res, 413) assert user.background == %{} clear_config([:instance, :upload_limit], upload_limit) From 57f74537486cf7f721679f125741de9008478b00 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Aug 2023 05:13:28 +0200 Subject: [PATCH 0304/1008] Release 2.5.3 --- CHANGELOG.md | 6 ++++++ mix.exs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6fc6aaee..468ec1012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Removed +## 2.5.3 + +### Security +- Emoji pack loader sanitizes pack names +- Reduced permissions of config files and directories, distros requiring greater permissions like group-read need to pre-create the directories + ## 2.5.2 ### Security diff --git a/mix.exs b/mix.exs index 79fd9c9ef..d1cdb151d 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.5.2"), + version: version("2.5.3"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), From 8cc8100120abdbf26cfe4cdac2c0a012d7919e05 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 22 Jun 2023 00:46:52 +0200 Subject: [PATCH 0305/1008] Config: Restrict permissions of OTP config file --- lib/pleroma/config/release_runtime_provider.ex | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex index 91e5f1a54..9ec0f975e 100644 --- a/lib/pleroma/config/release_runtime_provider.ex +++ b/lib/pleroma/config/release_runtime_provider.ex @@ -20,6 +20,20 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do with_runtime_config = if File.exists?(config_path) do + # + %File.Stat{mode: mode} = File.lstat!(config_path) + + if Bitwise.band(mode, 0o007) > 0 do + raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}" + end + + if Bitwise.band(mode, 0o020) > 0 do + raise "Configuration at #{config_path} has group-wise write permissions, execute the following: chmod g-w #{config_path}" + end + + # Note: Elixir doesn't provides a getuid(2) + # so cannot forbid group-read only when config is owned by us + runtime_config = Config.Reader.read!(config_path) with_defaults From 69caedc591bd61029f897f37ef7ecddd470cf935 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 22 Jun 2023 00:58:05 +0200 Subject: [PATCH 0306/1008] instance gen: Reduce permissions of pleroma directories and config files --- lib/mix/tasks/pleroma/instance.ex | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 5c93f19ff..5d8b254a2 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -266,12 +266,20 @@ defmodule Mix.Tasks.Pleroma.Instance do config_dir = Path.dirname(config_path) psql_dir = Path.dirname(psql_path) + # Note: Distros requiring group read (0o750) on those directories should + # pre-create the directories. [config_dir, psql_dir, static_dir, uploads_dir] |> Enum.reject(&File.exists?/1) - |> Enum.map(&File.mkdir_p!/1) + |> Enum.each(fn dir -> + File.mkdir_p!(dir) + File.chmod!(dir, 0o700) + end) shell_info("Writing config to #{config_path}.") + # Sadly no fchmod(2) equivalent in Elixir… + File.touch!(config_path) + File.chmod!(config_path, 0o640) File.write(config_path, result_config) shell_info("Writing the postgres script to #{psql_path}.") File.write(psql_path, result_psql) @@ -290,8 +298,7 @@ defmodule Mix.Tasks.Pleroma.Instance do else shell_error( "The task would have overwritten the following files:\n" <> - (Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <> - "Rerun with `--force` to overwrite them." + Enum.map_join(will_overwrite, &"- #{&1}\n") <> "Rerun with `--force` to overwrite them." ) end end From 9f0ad901ed4f8f0ad3e1d896fd41d25b93a97d76 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 22 Jun 2023 01:00:25 +0200 Subject: [PATCH 0307/1008] changelog: Entry for config permissions restrictions Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3135 --- changelog.d/otp_perms.security | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/otp_perms.security diff --git a/changelog.d/otp_perms.security b/changelog.d/otp_perms.security new file mode 100644 index 000000000..a3da1c677 --- /dev/null +++ b/changelog.d/otp_perms.security @@ -0,0 +1 @@ +- Reduced permissions of config files and directories, distros requiring greater permissions like group-read need to pre-create the directories \ No newline at end of file From 65ef8f19c5e21baadfa7daabdbd37fb9a1fbf863 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Aug 2023 07:16:50 +0200 Subject: [PATCH 0308/1008] release_runtime_provider_test: chmod config for hardened permissions Git doesn't manages file permissions precisely enough for us. --- test/pleroma/config/release_runtime_provider_test.exs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs index e6d10b13e..8d2a93d6c 100644 --- a/test/pleroma/config/release_runtime_provider_test.exs +++ b/test/pleroma/config/release_runtime_provider_test.exs @@ -17,6 +17,8 @@ defmodule Pleroma.Config.ReleaseRuntimeProviderTest do end test "merged runtime config" do + assert :ok == File.chmod!("test/fixtures/config/temp.secret.exs", 0o640) + merged = ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs") @@ -25,6 +27,8 @@ defmodule Pleroma.Config.ReleaseRuntimeProviderTest do end test "merged exported config" do + assert :ok == File.chmod!("test/fixtures/config/temp.exported_from_db.secret.exs", 0o640) + ExUnit.CaptureIO.capture_io(fn -> merged = ReleaseRuntimeProvider.load([], @@ -37,6 +41,9 @@ defmodule Pleroma.Config.ReleaseRuntimeProviderTest do end test "runtime config is merged with exported config" do + assert :ok == File.chmod!("test/fixtures/config/temp.secret.exs", 0o640) + assert :ok == File.chmod!("test/fixtures/config/temp.exported_from_db.secret.exs", 0o640) + merged = ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs", From 6a0fd77c48946b4b2100585b4f32d125680f5f82 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Aug 2023 05:13:28 +0200 Subject: [PATCH 0309/1008] Release 2.5.53 --- CHANGELOG.md | 6 ++++++ mix.exs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a1bbb8f..87e9c5298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Removed - BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) +## 2.5.3 + +### Security +- Emoji pack loader sanitizes pack names +- Reduced permissions of config files and directories, distros requiring greater permissions like group-read need to pre-create the directories + ## 2.5.2 ### Security diff --git a/mix.exs b/mix.exs index fc4e87ee3..115e7113f 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.5.52"), + version: version("2.5.53"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix] ++ Mix.compilers(), From 0e321698d21766772aa2b54b518dcd76a6abce8c Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 4 Aug 2023 17:09:50 +0200 Subject: [PATCH 0310/1008] gentoo_otp_en.md: Indicate which install method it covers --- changelog.d/gentoo_otp_intro.skip | 0 docs/installation/gentoo_otp_en.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/gentoo_otp_intro.skip diff --git a/changelog.d/gentoo_otp_intro.skip b/changelog.d/gentoo_otp_intro.skip new file mode 100644 index 000000000..e69de29bb diff --git a/docs/installation/gentoo_otp_en.md b/docs/installation/gentoo_otp_en.md index 76968eb38..4fafc0c17 100644 --- a/docs/installation/gentoo_otp_en.md +++ b/docs/installation/gentoo_otp_en.md @@ -2,7 +2,7 @@ {! backend/installation/otp_vs_from_source.include !} -A [manual installation guide for gentoo](./gentoo_en.md) is also available. +This guide covers installation via Gentoo provided packaging. A [manual installation guide for gentoo](./gentoo_en.md) is also available. ## Installation From ca0859b90f0f3cb9bb369d38d29868de59796c2c Mon Sep 17 00:00:00 2001 From: Mae Date: Fri, 4 Aug 2023 22:24:17 +0100 Subject: [PATCH 0311/1008] Prevent XML parser from loading external entities --- lib/pleroma/web/xml.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/xml.ex b/lib/pleroma/web/xml.ex index b699446b0..380a80ab8 100644 --- a/lib/pleroma/web/xml.ex +++ b/lib/pleroma/web/xml.ex @@ -29,7 +29,10 @@ defmodule Pleroma.Web.XML do {doc, _rest} = text |> :binary.bin_to_list() - |> :xmerl_scan.string(quiet: true) + |> :xmerl_scan.string( + quiet: true, + fetch_fun: fn _, _ -> raise "Resolving external entities not supported" end + ) {:ok, doc} rescue From 307692cee8cdd0dbe3e6cf40c1192fcf43910610 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 4 Aug 2023 22:24:32 +0100 Subject: [PATCH 0312/1008] Add unit test for external entity loading --- test/fixtures/xml_external_entities.xml | 3 +++ test/pleroma/web/web_finger_test.exs | 23 +++++++++++++++++++++++ test/pleroma/web/xml_test.exs | 10 ++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/fixtures/xml_external_entities.xml create mode 100644 test/pleroma/web/xml_test.exs diff --git a/test/fixtures/xml_external_entities.xml b/test/fixtures/xml_external_entities.xml new file mode 100644 index 000000000..d5ff87134 --- /dev/null +++ b/test/fixtures/xml_external_entities.xml @@ -0,0 +1,3 @@ + + ]> +&xxe; diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index fafef54fe..be5e08776 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -180,5 +180,28 @@ defmodule Pleroma.Web.WebFingerTest do {:ok, _data} = WebFinger.finger("pekorino@pawoo.net") end + + test "refuses to process XML remote entities" do + Tesla.Mock.mock(fn + %{ + url: "https://pawoo.net/.well-known/webfinger?resource=acct:pekorino@pawoo.net" + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/xml_external_entities.xml"), + headers: [{"content-type", "application/xrd+xml"}] + }} + + %{url: "https://pawoo.net/.well-known/host-meta"} -> + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/pawoo.net_host_meta") + }} + end) + + assert :error = WebFinger.finger("pekorino@pawoo.net") + end end end diff --git a/test/pleroma/web/xml_test.exs b/test/pleroma/web/xml_test.exs new file mode 100644 index 000000000..89d4709b6 --- /dev/null +++ b/test/pleroma/web/xml_test.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Web.XMLTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Web.XML + + test "refuses to load external entities from XML" do + data = File.read!("test/fixtures/xml_external_entities.xml") + assert(:error == XML.parse_document(data)) + end +end From 6d48b0f1a93a5a44b95497063e885342240fbc27 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Aug 2023 22:44:09 -0400 Subject: [PATCH 0313/1008] Document and test that XXE processing is disabled https://vuln.be/post/xxe-in-erlang-and-elixir/ --- changelog.d/akkoma-xml-remote-entities.security | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/akkoma-xml-remote-entities.security diff --git a/changelog.d/akkoma-xml-remote-entities.security b/changelog.d/akkoma-xml-remote-entities.security new file mode 100644 index 000000000..b3c86bee1 --- /dev/null +++ b/changelog.d/akkoma-xml-remote-entities.security @@ -0,0 +1 @@ +Restrict XML parser from processing external entitites (XXE) From fc10e07ffbc9d81c7a2ac38a3f9175f2edf2bd1f Mon Sep 17 00:00:00 2001 From: Mae Date: Fri, 4 Aug 2023 22:24:17 +0100 Subject: [PATCH 0314/1008] Prevent XML parser from loading external entities --- lib/pleroma/web/xml.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/xml.ex b/lib/pleroma/web/xml.ex index b699446b0..380a80ab8 100644 --- a/lib/pleroma/web/xml.ex +++ b/lib/pleroma/web/xml.ex @@ -29,7 +29,10 @@ defmodule Pleroma.Web.XML do {doc, _rest} = text |> :binary.bin_to_list() - |> :xmerl_scan.string(quiet: true) + |> :xmerl_scan.string( + quiet: true, + fetch_fun: fn _, _ -> raise "Resolving external entities not supported" end + ) {:ok, doc} rescue From 77d57c974ad83fcea77e424d53dc16a27e5d88b6 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 4 Aug 2023 22:24:32 +0100 Subject: [PATCH 0315/1008] Add unit test for external entity loading --- test/fixtures/xml_external_entities.xml | 3 +++ test/pleroma/web/web_finger_test.exs | 23 +++++++++++++++++++++++ test/pleroma/web/xml_test.exs | 10 ++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/fixtures/xml_external_entities.xml create mode 100644 test/pleroma/web/xml_test.exs diff --git a/test/fixtures/xml_external_entities.xml b/test/fixtures/xml_external_entities.xml new file mode 100644 index 000000000..d5ff87134 --- /dev/null +++ b/test/fixtures/xml_external_entities.xml @@ -0,0 +1,3 @@ + + ]> +&xxe; diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index fafef54fe..be5e08776 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -180,5 +180,28 @@ defmodule Pleroma.Web.WebFingerTest do {:ok, _data} = WebFinger.finger("pekorino@pawoo.net") end + + test "refuses to process XML remote entities" do + Tesla.Mock.mock(fn + %{ + url: "https://pawoo.net/.well-known/webfinger?resource=acct:pekorino@pawoo.net" + } -> + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/xml_external_entities.xml"), + headers: [{"content-type", "application/xrd+xml"}] + }} + + %{url: "https://pawoo.net/.well-known/host-meta"} -> + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/pawoo.net_host_meta") + }} + end) + + assert :error = WebFinger.finger("pekorino@pawoo.net") + end end end diff --git a/test/pleroma/web/xml_test.exs b/test/pleroma/web/xml_test.exs new file mode 100644 index 000000000..89d4709b6 --- /dev/null +++ b/test/pleroma/web/xml_test.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Web.XMLTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Web.XML + + test "refuses to load external entities from XML" do + data = File.read!("test/fixtures/xml_external_entities.xml") + assert(:error == XML.parse_document(data)) + end +end From cc848b78dca51fcd7e785eb92a7a3a4d5d1c419e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Aug 2023 22:44:09 -0400 Subject: [PATCH 0316/1008] Document and test that XXE processing is disabled https://vuln.be/post/xxe-in-erlang-and-elixir/ --- changelog.d/akkoma-xml-remote-entities.security | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/akkoma-xml-remote-entities.security diff --git a/changelog.d/akkoma-xml-remote-entities.security b/changelog.d/akkoma-xml-remote-entities.security new file mode 100644 index 000000000..b3c86bee1 --- /dev/null +++ b/changelog.d/akkoma-xml-remote-entities.security @@ -0,0 +1 @@ +Restrict XML parser from processing external entitites (XXE) From b631180b38ac63029f08bef137b13231bcf57b59 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sat, 5 Aug 2023 08:27:42 +0200 Subject: [PATCH 0317/1008] Release 2.5.4 --- CHANGELOG.md | 5 +++++ changelog.d/akkoma-xml-remote-entities.security | 2 +- mix.exs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 468ec1012..9d9aadc6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Removed +## 2.5.54 + +## Security +- Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem + ## 2.5.3 ### Security diff --git a/changelog.d/akkoma-xml-remote-entities.security b/changelog.d/akkoma-xml-remote-entities.security index b3c86bee1..5e6725e5b 100644 --- a/changelog.d/akkoma-xml-remote-entities.security +++ b/changelog.d/akkoma-xml-remote-entities.security @@ -1 +1 @@ -Restrict XML parser from processing external entitites (XXE) +Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem diff --git a/mix.exs b/mix.exs index d1cdb151d..12f721364 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.5.3"), + version: version("2.5.4"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), From 4099ddb3dc5840fa10cff743d87464acf7898a80 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sat, 5 Aug 2023 08:27:42 +0200 Subject: [PATCH 0318/1008] Mergeback release 2.5.4 --- CHANGELOG.md | 5 +++++ changelog.d/akkoma-xml-remote-entities.security | 2 +- mix.exs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e9c5298..65acfad3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Removed - BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) +## 2.5.4 + +## Security +- Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem + ## 2.5.3 ### Security diff --git a/changelog.d/akkoma-xml-remote-entities.security b/changelog.d/akkoma-xml-remote-entities.security index b3c86bee1..5e6725e5b 100644 --- a/changelog.d/akkoma-xml-remote-entities.security +++ b/changelog.d/akkoma-xml-remote-entities.security @@ -1 +1 @@ -Restrict XML parser from processing external entitites (XXE) +Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem diff --git a/mix.exs b/mix.exs index 115e7113f..b071e7c7b 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.5.53"), + version: version("2.5.54"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix] ++ Mix.compilers(), From 48b1e9bdc7382ec6ef33e95f2bd8674ae92f17b2 Mon Sep 17 00:00:00 2001 From: mae Date: Sat, 5 Aug 2023 14:13:49 +0200 Subject: [PATCH 0319/1008] Completely disable xml entity resolution --- .../disable-xml-entity-resolution.security | 1 + lib/pleroma/web/xml.ex | 2 +- test/fixtures/xml_billion_laughs.xml | 15 +++++++++++++++ test/pleroma/web/xml_test.exs | 5 +++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 changelog.d/disable-xml-entity-resolution.security create mode 100644 test/fixtures/xml_billion_laughs.xml diff --git a/changelog.d/disable-xml-entity-resolution.security b/changelog.d/disable-xml-entity-resolution.security new file mode 100644 index 000000000..db8e12f67 --- /dev/null +++ b/changelog.d/disable-xml-entity-resolution.security @@ -0,0 +1 @@ +Disable XML entity resolution completely to fix a dos vulnerability diff --git a/lib/pleroma/web/xml.ex b/lib/pleroma/web/xml.ex index 380a80ab8..64329e4ba 100644 --- a/lib/pleroma/web/xml.ex +++ b/lib/pleroma/web/xml.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.XML do |> :binary.bin_to_list() |> :xmerl_scan.string( quiet: true, - fetch_fun: fn _, _ -> raise "Resolving external entities not supported" end + allow_entities: false ) {:ok, doc} diff --git a/test/fixtures/xml_billion_laughs.xml b/test/fixtures/xml_billion_laughs.xml new file mode 100644 index 000000000..75fb24cae --- /dev/null +++ b/test/fixtures/xml_billion_laughs.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + +]> +&lol9; diff --git a/test/pleroma/web/xml_test.exs b/test/pleroma/web/xml_test.exs index 89d4709b6..49306430b 100644 --- a/test/pleroma/web/xml_test.exs +++ b/test/pleroma/web/xml_test.exs @@ -3,6 +3,11 @@ defmodule Pleroma.Web.XMLTest do alias Pleroma.Web.XML + test "refuses to parse any entities from XML" do + data = File.read!("test/fixtures/xml_billion_laughs.xml") + assert(:error == XML.parse_document(data)) + end + test "refuses to load external entities from XML" do data = File.read!("test/fixtures/xml_external_entities.xml") assert(:error == XML.parse_document(data)) From c298e0165c29b30380466910bb328964a7264c4c Mon Sep 17 00:00:00 2001 From: Cat pony Black Date: Sat, 5 Aug 2023 13:10:35 +0200 Subject: [PATCH 0320/1008] Fix config ownership in dockerfile to pass restriction test --- Dockerfile | 2 +- changelog.d/dockerfile-config-perms.fix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/dockerfile-config-perms.fix diff --git a/Dockerfile b/Dockerfile index 310d18104..72c227b76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,7 +49,7 @@ USER pleroma COPY --from=build --chown=pleroma:0 /release ${HOME} -COPY ./config/docker.exs /etc/pleroma/config.exs +COPY --chown=pleroma --chmod=640 ./config/docker.exs /etc/pleroma/config.exs COPY ./docker-entrypoint.sh ${HOME} EXPOSE 4000 diff --git a/changelog.d/dockerfile-config-perms.fix b/changelog.d/dockerfile-config-perms.fix new file mode 100644 index 000000000..49ea5becb --- /dev/null +++ b/changelog.d/dockerfile-config-perms.fix @@ -0,0 +1 @@ +- Fix config ownership in dockerfile to pass restriction test From 9effa24f308917f70276c41f91fb204b7684d942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 10 Aug 2023 22:52:38 +0200 Subject: [PATCH 0321/1008] Implement api/v2/instance route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/instance-v2.add | 1 + .../api_spec/operations/instance_operation.ex | 172 ++++++++++++++++++ .../controllers/instance_controller.ex | 5 + .../web/mastodon_api/views/instance_view.ex | 120 +++++++++--- lib/pleroma/web/router.ex | 3 + lib/pleroma/web/web_finger.ex | 2 +- .../controllers/instance_controller_test.exs | 7 + 7 files changed, 287 insertions(+), 23 deletions(-) create mode 100644 changelog.d/instance-v2.add diff --git a/changelog.d/instance-v2.add b/changelog.d/instance-v2.add new file mode 100644 index 000000000..4dd7ce8c0 --- /dev/null +++ b/changelog.d/instance-v2.add @@ -0,0 +1 @@ +Implement /api/v2/instance route \ No newline at end of file diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index a07be7e40..8e395bde8 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -23,6 +23,18 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def show2_operation do + %Operation{ + tags: ["Instance misc"], + summary: "Retrieve instance information", + description: "Information about the server", + operationId: "InstanceController.show2", + responses: %{ + 200 => Operation.response("Instance", "application/json", instance2()) + } + } + end + def peers_operation do %Operation{ tags: ["Instance misc"], @@ -165,6 +177,166 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + defp instance2 do + %Schema{ + type: :object, + properties: %{ + domain: %Schema{type: :string, description: "The domain name of the instance"}, + title: %Schema{type: :string, description: "The title of the website"}, + version: %Schema{ + type: :string, + description: "The version of Pleroma installed on the instance" + }, + source_url: %Schema{ + type: :string, + description: "The version of Pleroma installed on the instance" + }, + description: %Schema{ + type: :string, + description: "Admin-defined description of the Pleroma site" + }, + usage: %Schema{ + type: :object, + description: "Instance usage statistics", + properties: %{ + users: %Schema{ + type: :object, + description: "User count statistics", + properties: %{ + active_month: %Schema{ + type: :integer, + description: "Monthly active users" + } + } + } + } + }, + email: %Schema{ + type: :string, + description: "An email that may be contacted for any inquiries", + format: :email + }, + urls: %Schema{ + type: :object, + description: "URLs of interest for clients apps", + properties: %{} + }, + stats: %Schema{ + type: :object, + description: "Statistics about how much information the instance contains", + properties: %{ + user_count: %Schema{ + type: :integer, + description: "Users registered on this instance" + }, + status_count: %Schema{ + type: :integer, + description: "Statuses authored by users on instance" + }, + domain_count: %Schema{ + type: :integer, + description: "Domains federated with this instance" + } + } + }, + thumbnail: %Schema{ + type: :object, + properties: %{ + url: %Schema{ + type: :string, + description: "Banner image for the website", + nullable: true + } + } + }, + languages: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Primary langauges of the website and its staff" + }, + registrations: %Schema{ + type: :object, + description: "Registrations-related configuration", + properties: %{ + enabled: %Schema{ + type: :boolean, + description: "Whether registrations are enabled" + }, + approval_required: %Schema{ + type: :boolean, + description: "Whether users need to be manually approved by admin" + } + } + }, + configuration: %Schema{ + type: :object, + description: "Instance configuration", + properties: %{ + urls: %Schema{ + type: :object, + properties: %{ + streaming: %Schema{ + type: :string, + description: "Websockets address for push streaming" + } + } + }, + statuses: %Schema{ + type: :object, + description: "A map with poll limits for local statuses", + properties: %{ + max_characters: %Schema{ + type: :integer, + description: "Posts character limit (CW/Subject included in the counter)" + }, + max_media_attachments: %Schema{ + type: :integer, + description: "Media attachment limit" + } + } + }, + media_attachments: %Schema{ + type: :object, + description: "A map with poll limits for media attachments", + properties: %{ + image_size_limit: %Schema{ + type: :integer, + description: "File size limit of uploaded images" + }, + video_size_limit: %Schema{ + type: :integer, + description: "File size limit of uploaded videos" + } + } + }, + polls: %Schema{ + type: :object, + description: "A map with poll limits for local polls", + properties: %{ + max_options: %Schema{ + type: :integer, + description: "Maximum number of options." + }, + max_characters_per_option: %Schema{ + type: :integer, + description: "Maximum number of characters per option." + }, + min_expiration: %Schema{ + type: :integer, + description: "Minimum expiration time (in seconds)." + }, + max_expiration: %Schema{ + type: :integer, + description: "Maximum expiration time (in seconds)." + } + } + } + } + } + } + } + end + defp array_of_domains do %Schema{ type: :array, diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 6410e872c..3757a850a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -16,6 +16,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do render(conn, "show.json") end + @doc "GET /api/v2/instance" + def show2(conn, _params) do + render(conn, "show2.json") + end + @doc "GET /api/v1/instance/peers" def peers(conn, _params) do json(conn, Pleroma.Stats.get_peers()) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index efd2a0af6..c987cafd6 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do instance = Config.get(:instance) %{ - uri: Pleroma.Web.Endpoint.url(), + uri: Pleroma.Web.WebFinger.domain(), title: Keyword.get(instance, :name), description: Keyword.get(instance, :description), short_description: Keyword.get(instance, :short_description), @@ -30,6 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do languages: Keyword.get(instance, :languages, ["en"]), registrations: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), + configuration: configuration(), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), max_media_attachments: Keyword.get(instance, :max_media_attachments), @@ -41,19 +42,38 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), - pleroma: %{ - metadata: %{ - account_activation_required: Keyword.get(instance, :account_activation_required), - features: features(), - federation: federation(), - fields_limits: fields_limits(), - post_formats: Config.get([:instance, :allowed_post_formats]), - birthday_required: Config.get([:instance, :birthday_required]), - birthday_min_age: Config.get([:instance, :birthday_min_age]) - }, - stats: %{mau: Pleroma.User.active_user_count()}, - vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) - } + pleroma: pleroma_configuration(instance) + } + end + + def render("show2.json", _) do + instance = Config.get(:instance) + + %{ + domain: Pleroma.Web.WebFinger.domain(), + title: Keyword.get(instance, :name), + version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", + source_url: Pleroma.Application.repository(), + description: Keyword.get(instance, :short_description), + usage: %{users: %{active_month: Pleroma.User.active_user_count()}}, + thumbnail: %{ + url: + URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) + |> to_string + }, + languages: Keyword.get(instance, :languages, ["en"]), + configuration: configuration2(), + registrations: %{ + enabled: Keyword.get(instance, :registrations_open), + approval_required: Keyword.get(instance, :account_approval_required), + message: nil + }, + contact: %{ + email: Keyword.get(instance, :email), + account: nil + }, + # Extra (not present in Mastodon): + pleroma: pleroma_configuration2(instance) } end @@ -68,6 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "shareable_emoji_packs", "multifetch", "pleroma:api/v1/notifications:include_types_filter", + "quote_posting", "editing", if Config.get([:activitypub, :blockers_visible]) do "blockers_visible" @@ -78,13 +99,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do if Config.get([:gopher, :enabled]) do "gopher" end, - # backwards compat - if Config.get([:shout, :enabled]) do - "chat" - end, - if Config.get([:shout, :enabled]) do - "shout" - end, if Config.get([:instance, :allow_relay]) do "relay" end, @@ -94,6 +108,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "pleroma_emoji_reactions", "pleroma_custom_emoji_reactions", "pleroma_chat_messages", + "email_list", if Config.get([:instance, :show_reactions]) do "exposable_reactions" end, @@ -132,7 +147,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do |> Map.put(:enabled, Config.get([:instance, :federating])) end - def fields_limits do + defp fields_limits do %{ max_fields: Config.get([:instance, :max_account_fields]), max_remote_fields: Config.get([:instance, :max_remote_account_fields]), @@ -140,4 +155,65 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end + + defp configuration do + %{ + statuses: %{ + max_characters: Config.get([:instance, :limit]), + max_media_attachments: Config.get([:instance, :max_media_attachments]) + }, + media_attachments: %{ + image_size_limit: Config.get([:instance, :upload_limit]), + video_size_limit: Config.get([:instance, :upload_limit]) + }, + polls: %{ + max_options: Config.get([:instance, :poll_limits, :max_options]), + max_characters_per_option: Config.get([:instance, :poll_limits, :max_option_chars]), + min_expiration: Config.get([:instance, :poll_limits, :min_expiration]), + max_expiration: Config.get([:instance, :poll_limits, :max_expiration]) + } + } + end + + defp configuration2 do + configuration() + |> Map.merge(%{ + urls: %{streaming: Pleroma.Web.Endpoint.websocket_url()} + }) + end + + defp pleroma_configuration(instance) do + %{ + metadata: %{ + account_activation_required: Keyword.get(instance, :account_activation_required), + features: features(), + federation: federation(), + fields_limits: fields_limits(), + post_formats: Config.get([:instance, :allowed_post_formats]), + birthday_required: Config.get([:instance, :birthday_required]), + birthday_min_age: Config.get([:instance, :birthday_min_age]) + }, + stats: %{mau: Pleroma.User.active_user_count()}, + vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } + end + + defp pleroma_configuration2(instance) do + configuration = pleroma_configuration(instance) + + configuration + |> Map.merge(%{ + metadata: + configuration.metadata + |> Map.merge(%{ + avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit), + background_upload_limit: Keyword.get(instance, :background_upload_limit), + banner_upload_limit: Keyword.get(instance, :banner_upload_limit), + background_image: + Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), + description_limit: Keyword.get(instance, :description_limit), + shout_limit: Config.get([:shout, :limit]) + }) + }) + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6b9e158a3..5362100cc 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -774,11 +774,14 @@ defmodule Pleroma.Web.Router do scope "/api/v2", Pleroma.Web.MastodonAPI do pipe_through(:api) + get("/search", SearchController, :search2) post("/media", MediaController, :create2) get("/suggestions", SuggestionController, :index2) + + get("/instance", InstanceController, :show2) end scope "/api", Pleroma.Web do diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index f95dc2458..b28fad8d1 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -96,7 +96,7 @@ defmodule Pleroma.Web.WebFinger do |> XmlBuilder.to_doc() end - defp domain do + def domain do Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host() end diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index a556ef6a8..2243b0d4a 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -106,4 +106,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do |> get("/api/v1/instance") |> json_response_and_validate_schema(200) end + + test "get instance information v2", %{conn: conn} do + clear_config([:auth, :oauth_consumer_strategies], []) + + assert get(conn, "/api/v2/instance") + |> json_response_and_validate_schema(200) + end end From 79e46ce73f782a83654986adc9fd0b256be6a2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 11 Aug 2023 13:57:22 +0200 Subject: [PATCH 0322/1008] InstanceView: Add common_information function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../web/mastodon_api/views/instance_view.ex | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index c987cafd6..e7a2feb7f 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -13,12 +13,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do def render("show.json", _) do instance = Config.get(:instance) - %{ + common_information(instance) + |> Map.merge(%{ uri: Pleroma.Web.WebFinger.domain(), - title: Keyword.get(instance, :name), description: Keyword.get(instance, :description), short_description: Keyword.get(instance, :short_description), - version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", email: Keyword.get(instance, :email), urls: %{ streaming_api: Pleroma.Web.Endpoint.websocket_url() @@ -27,7 +26,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do thumbnail: URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) |> to_string, - languages: Keyword.get(instance, :languages, ["en"]), registrations: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), configuration: configuration(), @@ -43,16 +41,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), pleroma: pleroma_configuration(instance) - } + }) end def render("show2.json", _) do instance = Config.get(:instance) - %{ + common_information(instance) + |> Map.merge(%{ domain: Pleroma.Web.WebFinger.domain(), - title: Keyword.get(instance, :name), - version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", source_url: Pleroma.Application.repository(), description: Keyword.get(instance, :short_description), usage: %{users: %{active_month: Pleroma.User.active_user_count()}}, @@ -61,7 +58,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) |> to_string }, - languages: Keyword.get(instance, :languages, ["en"]), configuration: configuration2(), registrations: %{ enabled: Keyword.get(instance, :registrations_open), @@ -74,6 +70,14 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do }, # Extra (not present in Mastodon): pleroma: pleroma_configuration2(instance) + }) + end + + defp common_information(instance) do + %{ + title: Keyword.get(instance, :name), + version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", + languages: Keyword.get(instance, :languages, ["en"]) } end From d838d1990bf23d452c1cc830629e42e51dbd7047 Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Wed, 16 Aug 2023 13:34:32 +0000 Subject: [PATCH 0323/1008] Apply lanodan's suggestion(s) to 1 file(s) --- lib/pleroma/web/plugs/http_security_plug.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index b3dc8a3a6..a3166bc96 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do img_src = "img-src 'self' data: blob:" media_src = "media-src 'self'" - connect_src = ["connect-src 'self' blob: ", ?\s, websocket_url] + connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url] # Strict multimedia CSP enforcement only when MediaProxy is enabled {img_src, media_src, connect_src} = From 3d09bc320efcc68beb9b57fba23b6b9f3dc17905 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 30 Aug 2023 20:34:16 -0400 Subject: [PATCH 0324/1008] Make lint happy --- lib/pleroma/web/plugs/http_security_plug.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index a3166bc96..5093414c4 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -100,6 +100,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do if Config.get([:media_proxy, :enabled]) && !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do sources = build_csp_multimedia_source_list() + { [img_src, sources], [media_src, sources], @@ -113,7 +114,6 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do } end - connect_src = if Config.get(:env) == :dev do [connect_src, " http://localhost:3035/"] From 3c5ecca37718a1eba05be1f379b8f47362079c65 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 30 Aug 2023 20:37:45 -0400 Subject: [PATCH 0325/1008] Skip changelog --- changelog.d/lint.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/lint.skip diff --git a/changelog.d/lint.skip b/changelog.d/lint.skip new file mode 100644 index 000000000..e69de29bb From 1afde067b12ad0062c1820091ea9b0a680819281 Mon Sep 17 00:00:00 2001 From: Mint Date: Sat, 2 Sep 2023 01:43:25 +0300 Subject: [PATCH 0326/1008] CommonAPI: Prevent users from accessing media of other users --- .../check-attachment-attribution.security | 1 + lib/pleroma/scheduled_activity.ex | 6 ++- lib/pleroma/web/common_api.ex | 12 ++++++ lib/pleroma/web/common_api/activity_draft.ex | 2 +- lib/pleroma/web/common_api/utils.ex | 27 ++++++------ test/pleroma/web/common_api/utils_test.exs | 43 +++++++++++++------ test/pleroma/web/common_api_test.exs | 18 ++++++++ .../views/scheduled_activity_view_test.exs | 2 +- .../chat_message_reference_view_test.exs | 2 +- 9 files changed, 82 insertions(+), 31 deletions(-) create mode 100644 changelog.d/check-attachment-attribution.security diff --git a/changelog.d/check-attachment-attribution.security b/changelog.d/check-attachment-attribution.security new file mode 100644 index 000000000..e0e46525b --- /dev/null +++ b/changelog.d/check-attachment-attribution.security @@ -0,0 +1 @@ +CommonAPI: Prevent users from accessing media of other users by creating a status with reused attachment ID diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex index a7be58512..0ed51ad07 100644 --- a/lib/pleroma/scheduled_activity.ex +++ b/lib/pleroma/scheduled_activity.ex @@ -40,7 +40,11 @@ defmodule Pleroma.ScheduledActivity do %{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset ) when is_list(media_ids) do - media_attachments = Utils.attachments_from_ids(%{media_ids: media_ids}) + media_attachments = + Utils.attachments_from_ids( + %{media_ids: media_ids}, + User.get_cached_by_id(changeset.data.user_id) + ) params = params diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 77b3fa5d2..82c7f70d2 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -33,6 +33,7 @@ defmodule Pleroma.Web.CommonAPI do def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]), + :ok <- validate_chat_attachment_attribution(maybe_attachment, user), :ok <- validate_chat_content_length(content, !!maybe_attachment), {_, {:ok, chat_message_data, _meta}} <- {:build_object, @@ -71,6 +72,17 @@ defmodule Pleroma.Web.CommonAPI do text end + defp validate_chat_attachment_attribution(nil, _), do: :ok + + defp validate_chat_attachment_attribution(attachment, user) do + with :ok <- Object.authorize_access(attachment, user) do + :ok + else + e -> + e + end + end + defp validate_chat_content_length(_, true), do: :ok defp validate_chat_content_length(nil, false), do: {:error, :no_content} diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 9af635da8..63ed48a27 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -111,7 +111,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end defp attachments(%{params: params} = draft) do - attachments = Utils.attachments_from_ids(params) + attachments = Utils.attachments_from_ids(params, draft.user) draft = %__MODULE__{draft | attachments: attachments} case Utils.validate_attachments_count(attachments) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index b9fe0224c..0f394e951 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -23,21 +23,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do require Logger require Pleroma.Constants - def attachments_from_ids(%{media_ids: ids, descriptions: desc}) do - attachments_from_ids_descs(ids, desc) + def attachments_from_ids(%{media_ids: ids, descriptions: desc}, user) do + attachments_from_ids_descs(ids, desc, user) end - def attachments_from_ids(%{media_ids: ids}) do - attachments_from_ids_no_descs(ids) + def attachments_from_ids(%{media_ids: ids}, user) do + attachments_from_ids_no_descs(ids, user) end - def attachments_from_ids(_), do: [] + def attachments_from_ids(_, _), do: [] - def attachments_from_ids_no_descs([]), do: [] + def attachments_from_ids_no_descs([], _), do: [] - def attachments_from_ids_no_descs(ids) do + def attachments_from_ids_no_descs(ids, user) do Enum.map(ids, fn media_id -> - case get_attachment(media_id) do + case get_attachment(media_id, user) do %Object{data: data} -> data _ -> nil end @@ -45,22 +45,23 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Enum.reject(&is_nil/1) end - def attachments_from_ids_descs([], _), do: [] + def attachments_from_ids_descs([], _, _), do: [] - def attachments_from_ids_descs(ids, descs_str) do + def attachments_from_ids_descs(ids, descs_str, user) do {_, descs} = Jason.decode(descs_str) Enum.map(ids, fn media_id -> - with %Object{data: data} <- get_attachment(media_id) do + with %Object{data: data} <- get_attachment(media_id, user) do Map.put(data, "name", descs[media_id]) end end) |> Enum.reject(&is_nil/1) end - defp get_attachment(media_id) do + defp get_attachment(media_id, user) do with %Object{data: data} = object <- Repo.get(Object, media_id), - %{"type" => type} when type in Pleroma.Constants.upload_object_types() <- data do + %{"type" => type} when type in Pleroma.Constants.upload_object_types() <- data, + :ok <- Object.authorize_access(object, user) do object else _ -> nil diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index ca5b92683..4ce039d64 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -586,46 +586,61 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do end end - describe "attachments_from_ids_descs/2" do + describe "attachments_from_ids_descs/3" do test "returns [] when attachment ids is empty" do - assert Utils.attachments_from_ids_descs([], "{}") == [] + assert Utils.attachments_from_ids_descs([], "{}", nil) == [] end test "returns list attachments with desc" do - object = insert(:attachment) + user = insert(:user) + object = insert(:attachment, %{user: user}) desc = Jason.encode!(%{object.id => "test-desc"}) - assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [ + assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc, user) == [ Map.merge(object.data, %{"name" => "test-desc"}) ] end end - describe "attachments_from_ids/1" do + describe "attachments_from_ids/2" do test "returns attachments with descs" do - object = insert(:attachment) + user = insert(:user) + object = insert(:attachment, %{user: user}) desc = Jason.encode!(%{object.id => "test-desc"}) - assert Utils.attachments_from_ids(%{ - media_ids: ["#{object.id}"], - descriptions: desc - }) == [ + assert Utils.attachments_from_ids( + %{ + media_ids: ["#{object.id}"], + descriptions: desc + }, + user + ) == [ Map.merge(object.data, %{"name" => "test-desc"}) ] end test "returns attachments without descs" do - object = insert(:attachment) - assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [object.data] + user = insert(:user) + object = insert(:attachment, %{user: user}) + assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}, user) == [object.data] end test "returns [] when not pass media_ids" do - assert Utils.attachments_from_ids(%{}) == [] + assert Utils.attachments_from_ids(%{}, nil) == [] + end + + test "returns [] when media_ids not belong to current user" do + user = insert(:user) + user2 = insert(:user) + + object = insert(:attachment, %{user: user}) + + assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}, user2) == [] end test "checks that the object is of upload type" do object = insert(:note) - assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [] + assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}, nil) == [] end end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 968e11a14..0d76d6581 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -279,6 +279,24 @@ defmodule Pleroma.Web.CommonAPITest do assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} == CommonAPI.post_chat_message(author, recipient, "GNO/Linux") end + + test "it reject messages with attachments not belonging to user" do + author = insert(:user) + not_author = insert(:user) + recipient = author + + attachment = insert(:attachment, %{user: not_author}) + + {:error, message} = + CommonAPI.post_chat_message( + author, + recipient, + "123", + media_id: attachment.id + ) + + assert message == :forbidden + end end describe "unblocking" do diff --git a/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs index e5e510d33..07a65a3bc 100644 --- a/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs @@ -48,7 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do id: to_string(scheduled_activity.id), media_attachments: %{media_ids: [upload.id]} - |> Utils.attachments_from_ids() + |> Utils.attachments_from_ids(user) |> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})), params: %{ in_reply_to_id: to_string(activity.id), diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs index 017c9c5c0..7ab3f5acd 100644 --- a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs +++ b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs @@ -24,7 +24,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do filename: "an_image.jpg" } - {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + {:ok, upload} = ActivityPub.upload(file, actor: recipient.ap_id) {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:", idempotency_key: "123") From 535a5ecad04c9c49105a77e7025fe9f4b4d23ba6 Mon Sep 17 00:00:00 2001 From: Mint Date: Sat, 2 Sep 2023 01:43:25 +0300 Subject: [PATCH 0327/1008] CommonAPI: Prevent users from accessing media of other users commit 1afde067b12ad0062c1820091ea9b0a680819281 upstream. --- .../check-attachment-attribution.security | 1 + lib/pleroma/scheduled_activity.ex | 6 ++- lib/pleroma/web/common_api.ex | 12 ++++++ lib/pleroma/web/common_api/activity_draft.ex | 2 +- lib/pleroma/web/common_api/utils.ex | 31 ++++++++------ test/pleroma/web/common_api/utils_test.exs | 41 +++++++++++++------ test/pleroma/web/common_api_test.exs | 18 ++++++++ .../views/scheduled_activity_view_test.exs | 2 +- .../chat_message_reference_view_test.exs | 2 +- 9 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 changelog.d/check-attachment-attribution.security diff --git a/changelog.d/check-attachment-attribution.security b/changelog.d/check-attachment-attribution.security new file mode 100644 index 000000000..e0e46525b --- /dev/null +++ b/changelog.d/check-attachment-attribution.security @@ -0,0 +1 @@ +CommonAPI: Prevent users from accessing media of other users by creating a status with reused attachment ID diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex index a7be58512..0ed51ad07 100644 --- a/lib/pleroma/scheduled_activity.ex +++ b/lib/pleroma/scheduled_activity.ex @@ -40,7 +40,11 @@ defmodule Pleroma.ScheduledActivity do %{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset ) when is_list(media_ids) do - media_attachments = Utils.attachments_from_ids(%{media_ids: media_ids}) + media_attachments = + Utils.attachments_from_ids( + %{media_ids: media_ids}, + User.get_cached_by_id(changeset.data.user_id) + ) params = params diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 89cc0d6fe..44eb00075 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -33,6 +33,7 @@ defmodule Pleroma.Web.CommonAPI do def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]), + :ok <- validate_chat_attachment_attribution(maybe_attachment, user), :ok <- validate_chat_content_length(content, !!maybe_attachment), {_, {:ok, chat_message_data, _meta}} <- {:build_object, @@ -71,6 +72,17 @@ defmodule Pleroma.Web.CommonAPI do text end + defp validate_chat_attachment_attribution(nil, _), do: :ok + + defp validate_chat_attachment_attribution(attachment, user) do + with :ok <- Object.authorize_access(attachment, user) do + :ok + else + e -> + e + end + end + defp validate_chat_content_length(_, true), do: :ok defp validate_chat_content_length(nil, false), do: {:error, :no_content} diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 9af635da8..63ed48a27 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -111,7 +111,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end defp attachments(%{params: params} = draft) do - attachments = Utils.attachments_from_ids(params) + attachments = Utils.attachments_from_ids(params, draft.user) draft = %__MODULE__{draft | attachments: attachments} case Utils.validate_attachments_count(attachments) do diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index ff0814329..6410815ea 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -23,21 +23,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do require Logger require Pleroma.Constants - def attachments_from_ids(%{media_ids: ids, descriptions: desc}) do - attachments_from_ids_descs(ids, desc) + def attachments_from_ids(%{media_ids: ids, descriptions: desc}, user) do + attachments_from_ids_descs(ids, desc, user) end - def attachments_from_ids(%{media_ids: ids}) do - attachments_from_ids_no_descs(ids) + def attachments_from_ids(%{media_ids: ids}, user) do + attachments_from_ids_no_descs(ids, user) end - def attachments_from_ids(_), do: [] + def attachments_from_ids(_, _), do: [] - def attachments_from_ids_no_descs([]), do: [] + def attachments_from_ids_no_descs([], _), do: [] - def attachments_from_ids_no_descs(ids) do + def attachments_from_ids_no_descs(ids, user) do Enum.map(ids, fn media_id -> - case get_attachment(media_id) do + case get_attachment(media_id, user) do %Object{data: data} -> data _ -> nil end @@ -45,21 +45,26 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Enum.reject(&is_nil/1) end - def attachments_from_ids_descs([], _), do: [] + def attachments_from_ids_descs([], _, _), do: [] - def attachments_from_ids_descs(ids, descs_str) do + def attachments_from_ids_descs(ids, descs_str, user) do {_, descs} = Jason.decode(descs_str) Enum.map(ids, fn media_id -> - with %Object{data: data} <- get_attachment(media_id) do + with %Object{data: data} <- get_attachment(media_id, user) do Map.put(data, "name", descs[media_id]) end end) |> Enum.reject(&is_nil/1) end - defp get_attachment(media_id) do - Repo.get(Object, media_id) + defp get_attachment(media_id, user) do + with %Object{data: _data} = object <- Repo.get(Object, media_id), + :ok <- Object.authorize_access(object, user) do + object + else + _ -> nil + end end @spec get_to_and_cc(ActivityDraft.t()) :: {list(String.t()), list(String.t())} diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index d309c6ded..c52d3e9c5 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -586,41 +586,56 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do end end - describe "attachments_from_ids_descs/2" do + describe "attachments_from_ids_descs/3" do test "returns [] when attachment ids is empty" do - assert Utils.attachments_from_ids_descs([], "{}") == [] + assert Utils.attachments_from_ids_descs([], "{}", nil) == [] end test "returns list attachments with desc" do - object = insert(:note) + user = insert(:user) + object = insert(:note, %{user: user}) desc = Jason.encode!(%{object.id => "test-desc"}) - assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [ + assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc, user) == [ Map.merge(object.data, %{"name" => "test-desc"}) ] end end - describe "attachments_from_ids/1" do + describe "attachments_from_ids/2" do test "returns attachments with descs" do - object = insert(:note) + user = insert(:user) + object = insert(:note, %{user: user}) desc = Jason.encode!(%{object.id => "test-desc"}) - assert Utils.attachments_from_ids(%{ - media_ids: ["#{object.id}"], - descriptions: desc - }) == [ + assert Utils.attachments_from_ids( + %{ + media_ids: ["#{object.id}"], + descriptions: desc + }, + user + ) == [ Map.merge(object.data, %{"name" => "test-desc"}) ] end test "returns attachments without descs" do - object = insert(:note) - assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [object.data] + user = insert(:user) + object = insert(:note, %{user: user}) + assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}, user) == [object.data] end test "returns [] when not pass media_ids" do - assert Utils.attachments_from_ids(%{}) == [] + assert Utils.attachments_from_ids(%{}, nil) == [] + end + + test "returns [] when media_ids not belong to current user" do + user = insert(:user) + user2 = insert(:user) + + object = insert(:attachment, %{user: user}) + + assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}, user2) == [] end end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 5c9103e9f..e60691995 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -279,6 +279,24 @@ defmodule Pleroma.Web.CommonAPITest do assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} == CommonAPI.post_chat_message(author, recipient, "GNO/Linux") end + + test "it reject messages with attachments not belonging to user" do + author = insert(:user) + not_author = insert(:user) + recipient = author + + attachment = insert(:attachment, %{user: not_author}) + + {:error, message} = + CommonAPI.post_chat_message( + author, + recipient, + "123", + media_id: attachment.id + ) + + assert message == :forbidden + end end describe "unblocking" do diff --git a/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs index e5e510d33..07a65a3bc 100644 --- a/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs @@ -48,7 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do id: to_string(scheduled_activity.id), media_attachments: %{media_ids: [upload.id]} - |> Utils.attachments_from_ids() + |> Utils.attachments_from_ids(user) |> Enum.map(&StatusView.render("attachment.json", %{attachment: &1})), params: %{ in_reply_to_id: to_string(activity.id), diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs index 017c9c5c0..7ab3f5acd 100644 --- a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs +++ b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs @@ -24,7 +24,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do filename: "an_image.jpg" } - {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + {:ok, upload} = ActivityPub.upload(file, actor: recipient.ap_id) {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:", idempotency_key: "123") From 385492577d11e9667064d7f7e0dacdc00457064a Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 23 Dec 2022 18:46:14 +0100 Subject: [PATCH 0328/1008] mix: version 2.5.5 --- CHANGELOG.md | 7 ++++++- mix.exs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d9aadc6e..32ec440de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Removed -## 2.5.54 +## 2.5.5 + +## Security +- Prevent users from accessing media of other users by creating a status with reused attachment ID + +## 2.5.4 ## Security - Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem diff --git a/mix.exs b/mix.exs index 12f721364..e2aac0fc5 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.5.4"), + version: version("2.5.5"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), From 28ef5ebd3c7c2fc083424dbf4434392fcd2a7aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 7 Sep 2023 15:00:24 +0200 Subject: [PATCH 0329/1008] Update InstanceView.features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index e7a2feb7f..c1cecf6e6 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -92,7 +92,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "shareable_emoji_packs", "multifetch", "pleroma:api/v1/notifications:include_types_filter", - "quote_posting", "editing", if Config.get([:activitypub, :blockers_visible]) do "blockers_visible" @@ -103,6 +102,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do if Config.get([:gopher, :enabled]) do "gopher" end, + # backwards compat + if Config.get([:shout, :enabled]) do + "chat" + end, + if Config.get([:shout, :enabled]) do + "shout" + end, if Config.get([:instance, :allow_relay]) do "relay" end, @@ -112,7 +118,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "pleroma_emoji_reactions", "pleroma_custom_emoji_reactions", "pleroma_chat_messages", - "email_list", if Config.get([:instance, :show_reactions]) do "exposable_reactions" end, From 31eb3dc24587ad3715da9fe00886867a6c0bf0c4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 16:41:30 -0600 Subject: [PATCH 0330/1008] ObjectValidators: accept "quoteUrl" field --- .../web/activity_pub/object_validators/common_fields.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex index d580208df..835ed97b7 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fields.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fields.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do end end - # All objects except Answer and CHatMessage + # All objects except Answer and ChatMessage defmacro object_fields do quote bind_quoted: binding() do field(:content, :string) @@ -58,6 +58,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do field(:like_count, :integer, default: 0) field(:announcement_count, :integer, default: 0) field(:inReplyTo, ObjectValidators.ObjectID) + field(:quoteUrl, ObjectValidators.ObjectID) field(:url, ObjectValidators.BareUri) field(:likes, {:array, ObjectValidators.ObjectID}, default: []) From 7deda1fa18cb95a588ba66950ac45262c467a7f4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 17:30:49 -0600 Subject: [PATCH 0331/1008] Quote post: add fixtures --- .../quote_post/fedibird_quote_post.json | 52 +++++++++++++++++++ .../quote_post/misskey_quote_post.json | 46 ++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 test/fixtures/quote_post/fedibird_quote_post.json create mode 100644 test/fixtures/quote_post/misskey_quote_post.json diff --git a/test/fixtures/quote_post/fedibird_quote_post.json b/test/fixtures/quote_post/fedibird_quote_post.json new file mode 100644 index 000000000..ebf383356 --- /dev/null +++ b/test/fixtures/quote_post/fedibird_quote_post.json @@ -0,0 +1,52 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + { + "ostatus": "http://ostatus.org#", + "atomUri": "ostatus:atomUri", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "sensitive": "as:sensitive", + "toot": "http://joinmastodon.org/ns#", + "votersCount": "toot:votersCount", + "expiry": "toot:expiry" + } + ], + "id": "https://fedibird.com/users/noellabo/statuses/107663670404015196", + "type": "Note", + "summary": null, + "inReplyTo": null, + "published": "2022-01-22T02:07:16Z", + "url": "https://fedibird.com/@noellabo/107663670404015196", + "attributedTo": "https://fedibird.com/users/noellabo", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://fedibird.com/users/noellabo/followers" + ], + "sensitive": false, + "atomUri": "https://fedibird.com/users/noellabo/statuses/107663670404015196", + "inReplyToAtomUri": null, + "conversation": "tag:fedibird.com,2022-01-22:objectId=107663670404038002:objectType=Conversation", + "context": "https://fedibird.com/contexts/107663670404038002", + "quoteURL": "https://misskey.io/notes/8vsn2izjwh", + "_misskey_quote": "https://misskey.io/notes/8vsn2izjwh", + "_misskey_content": "いつの生まれだシトリン", + "content": "

いつの生まれだシトリン
QT: https://misskey.io/notes/8vsn2izjwh

", + "contentMap": { + "ja": "

いつの生まれだシトリン
QT: https://misskey.io/notes/8vsn2izjwh

" + }, + "attachment": [], + "tag": [], + "replies": { + "id": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies", + "type": "Collection", + "first": { + "type": "CollectionPage", + "next": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies?only_other_accounts=true&page=true", + "partOf": "https://fedibird.com/users/noellabo/statuses/107663670404015196/replies", + "items": [] + } + } +} diff --git a/test/fixtures/quote_post/misskey_quote_post.json b/test/fixtures/quote_post/misskey_quote_post.json new file mode 100644 index 000000000..59f677ca9 --- /dev/null +++ b/test/fixtures/quote_post/misskey_quote_post.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "sensitive": "as:sensitive", + "Hashtag": "as:Hashtag", + "quoteUrl": "as:quoteUrl", + "toot": "http://joinmastodon.org/ns#", + "Emoji": "toot:Emoji", + "featured": "toot:featured", + "discoverable": "toot:discoverable", + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + "misskey": "https://misskey.io/ns#", + "_misskey_content": "misskey:_misskey_content", + "_misskey_quote": "misskey:_misskey_quote", + "_misskey_reaction": "misskey:_misskey_reaction", + "_misskey_votes": "misskey:_misskey_votes", + "_misskey_talk": "misskey:_misskey_talk", + "isCat": "misskey:isCat", + "vcard": "http://www.w3.org/2006/vcard/ns#" + } + ], + "id": "https://misskey.io/notes/8vs6ylpfez", + "type": "Note", + "attributedTo": "https://misskey.io/users/7rkrarq81i", + "summary": null, + "content": "

投稿者の設定によるね
Fanboxについても投稿者によっては過去の投稿は高額なプランに移動してることがある

RE:
https://misskey.io/notes/8vs6wxufd0

", + "_misskey_content": "投稿者の設定によるね\nFanboxについても投稿者によっては過去の投稿は高額なプランに移動してることがある", + "_misskey_quote": "https://misskey.io/notes/8vs6wxufd0", + "quoteUrl": "https://misskey.io/notes/8vs6wxufd0", + "published": "2022-01-21T16:38:30.243Z", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://misskey.io/users/7rkrarq81i/followers" + ], + "inReplyTo": null, + "attachment": [], + "sensitive": false, + "tag": [] +} From 795736af16dca77929725e7dd55f5de04a796fdb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 18:03:22 -0600 Subject: [PATCH 0332/1008] ObjectValidators: improve quoteUrl compatibility --- .../article_note_page_validator.ex | 16 +++++++++++++++ .../article_note_page_validator_test.exs | 20 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index 2670e3f17..40bb67934 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -76,6 +76,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do def fix_attachments(data), do: data + defp fix_quote_url(%{"quoteUrl" => _quote_url} = data), do: data + + # Fix for Fedibird + # https://github.com/fedibird/mastodon/issues/9 + defp fix_quote_url(%{"quoteURL" => quote_url} = data) do + Map.put(data, "quoteUrl", quote_url) + end + + # Misskey fallback + defp fix_quote_url(%{"_misskey_quote" => quote_url} = data) do + Map.put(data, "quoteUrl", quote_url) + end + + defp fix_quote_url(data), do: data + defp fix(data) do data |> CommonFixes.fix_actor() @@ -84,6 +99,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do |> fix_tag() |> fix_replies() |> fix_attachments() + |> fix_quote_url() |> Transmogrifier.fix_emoji() |> Transmogrifier.fix_content_map() end diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index c7a62be18..c3cde00b5 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -116,4 +116,24 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) end + + test "Fedibird quote post" do + insert(:user, ap_id: "https://fedibird.com/users/noellabo") + + data = File.read!("test/fixtures/quote_post/fedibird_quote_post.json") |> Jason.decode!() + chg = ArticleNotePageValidator.cast_and_validate(data) + + assert chg.valid? + assert chg.changes.quoteUrl == "https://misskey.io/notes/8vsn2izjwh" + end + + test "Misskey quote post" do + insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i") + + data = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!() + chg = ArticleNotePageValidator.cast_and_validate(data) + + assert chg.valid? + assert chg.changes.quoteUrl == "https://misskey.io/notes/8vs6wxufd0" + end end From b022d6635dad4b2769fbf1fd4b97f77a4cc646b4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 18:46:58 -0600 Subject: [PATCH 0333/1008] Transmogrifier: fetch quoted post --- .../web/activity_pub/transmogrifier.ex | 17 +++++ test/fixtures/tesla_mock/aimu@misskey.io.json | 64 +++++++++++++++++++ .../tesla_mock/misskey.io_8vs6wxufd0.json | 44 +++++++++++++ .../web/activity_pub/transmogrifier_test.exs | 22 +++++++ test/support/http_request_mock.ex | 18 ++++++ 5 files changed, 165 insertions(+) create mode 100644 test/fixtures/tesla_mock/aimu@misskey.io.json create mode 100644 test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 0e6c429f9..c466271ca 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -166,6 +166,22 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_in_reply_to(object, _options), do: object + def fix_quote(object, options \\ []) + + def fix_quote(%{"quoteUrl" => quote_url} = object, options) + when not is_nil(quote_url) do + with {:ok, quoted_object} <- get_obj_helper(quote_url, options), + %Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do + Map.put(object, "quoteUrl", quoted_object.data["id"]) + else + e -> + Logger.warn("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}") + object + end + end + + def fix_quote(object, _options), do: object + defp prepare_in_reply_to(in_reply_to) do cond do is_bitstring(in_reply_to) -> @@ -454,6 +470,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> strip_internal_fields() |> fix_type(fetch_options) |> fix_in_reply_to(fetch_options) + |> fix_quote(fetch_options) data = Map.put(data, "object", object) options = Keyword.put(options, :local, false) diff --git a/test/fixtures/tesla_mock/aimu@misskey.io.json b/test/fixtures/tesla_mock/aimu@misskey.io.json new file mode 100644 index 000000000..9ff4cb6d0 --- /dev/null +++ b/test/fixtures/tesla_mock/aimu@misskey.io.json @@ -0,0 +1,64 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "sensitive": "as:sensitive", + "Hashtag": "as:Hashtag", + "quoteUrl": "as:quoteUrl", + "toot": "http://joinmastodon.org/ns#", + "Emoji": "toot:Emoji", + "featured": "toot:featured", + "discoverable": "toot:discoverable", + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + "misskey": "https://misskey.io/ns#", + "_misskey_content": "misskey:_misskey_content", + "_misskey_quote": "misskey:_misskey_quote", + "_misskey_reaction": "misskey:_misskey_reaction", + "_misskey_votes": "misskey:_misskey_votes", + "_misskey_talk": "misskey:_misskey_talk", + "isCat": "misskey:isCat", + "vcard": "http://www.w3.org/2006/vcard/ns#" + } + ], + "type": "Person", + "id": "https://misskey.io/users/83ssedkv53", + "inbox": "https://misskey.io/users/83ssedkv53/inbox", + "outbox": "https://misskey.io/users/83ssedkv53/outbox", + "followers": "https://misskey.io/users/83ssedkv53/followers", + "following": "https://misskey.io/users/83ssedkv53/following", + "sharedInbox": "https://misskey.io/inbox", + "endpoints": { + "sharedInbox": "https://misskey.io/inbox" + }, + "url": "https://misskey.io/@aimu", + "preferredUsername": "aimu", + "name": "あいむ", + "summary": "

わずかな作曲要素 巣穴で独り言
Twitter
https://twitter.com/aimu_53
Soundcloud
https://soundcloud.com/aimu-53

", + "icon": { + "type": "Image", + "url": "https://s3.arkjp.net/misskey/webpublic-3f7e93c0-34f5-443c-acc0-f415cb2342b4.jpg", + "sensitive": false, + "name": null + }, + "image": { + "type": "Image", + "url": "https://s3.arkjp.net/misskey/webpublic-2db63d1d-490b-488b-ab62-c93c285f26b6.png", + "sensitive": false, + "name": null + }, + "tag": [], + "manuallyApprovesFollowers": false, + "discoverable": true, + "publicKey": { + "id": "https://misskey.io/users/83ssedkv53#main-key", + "type": "Key", + "owner": "https://misskey.io/users/83ssedkv53", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1ylhePJ6qGHmwHSBP17b\nIosxGaiFKvgDBgZdm8vzvKeRSqJV9uLHfZL3pO/Zt02EwaZd2GohZAtBZEF8DbMA\n3s93WAesvyGF9mjGrYYKlhp/glwyrrrbf+RdD0DLtyDwRRlrxp3pS2lLmv5Tp1Zl\npH+UKpOnNrpQqjHI5P+lEc9bnflzbRrX+UiyLNsVAP80v4wt7SZfT/telrU6mDru\n998UdfhUo7bDKeDsHG1PfLpyhhtfdoZub4kBpkyacHiwAd+CdCjR54Eu7FDwVK3p\nY3JcrT2q5stgMqN1m4QgSL4XAADIotWwDYttTJejM1n9dr+6VWv5bs0F2Q/6gxOp\nu5DQZLk4Q+64U4LWNox6jCMOq3fYe0g7QalJIHnanYQQo+XjoH6S1Aw64gQ3Ip2Y\nZBmZREAOR7GMFVDPFnVnsbCHnIAv16TdgtLgQBAihkWEUuPqITLi8PMu6kMr3uyq\nYkObEfH0TNTcqaiVpoXv791GZLEUV5ROl0FSUANLNkHZZv29xZ5JDOBOR1rNBLyH\ngVtW8rpszYqOXwzX23hh4WsVXfB7YgNvIijwjiaWbzsecleaENGEnLNMiVKVumTj\nmtyTeFJpH0+OaSrUYpemRRJizmqIjklKsNwUEwUb2WcUUg92o56T2obrBkooabZe\nwgSXSKTOcjsR/ju7+AuIyvkCAwEAAQ==\n-----END PUBLIC KEY-----\n" + }, + "isCat": true, + "vcard:bday": "5353-05-03" +} diff --git a/test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json b/test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json new file mode 100644 index 000000000..323ca10ed --- /dev/null +++ b/test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json @@ -0,0 +1,44 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "sensitive": "as:sensitive", + "Hashtag": "as:Hashtag", + "quoteUrl": "as:quoteUrl", + "toot": "http://joinmastodon.org/ns#", + "Emoji": "toot:Emoji", + "featured": "toot:featured", + "discoverable": "toot:discoverable", + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + "misskey": "https://misskey.io/ns#", + "_misskey_content": "misskey:_misskey_content", + "_misskey_quote": "misskey:_misskey_quote", + "_misskey_reaction": "misskey:_misskey_reaction", + "_misskey_votes": "misskey:_misskey_votes", + "_misskey_talk": "misskey:_misskey_talk", + "isCat": "misskey:isCat", + "vcard": "http://www.w3.org/2006/vcard/ns#" + } + ], + "id": "https://misskey.io/notes/8vs6wxufd0", + "type": "Note", + "attributedTo": "https://misskey.io/users/83ssedkv53", + "summary": null, + "content": "

Fantiaこれできないように過去のやつは従量課金だった気がする

", + "_misskey_content": "Fantiaこれできないように過去のやつは従量課金だった気がする", + "published": "2022-01-21T16:37:12.663Z", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://misskey.io/users/83ssedkv53/followers" + ], + "inReplyTo": null, + "attachment": [], + "sensitive": false, + "tag": [] +} diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 3e0c8dc65..2c8e5ba21 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -136,6 +136,28 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do tag = object.data["tag"] |> List.first() assert tag["type"] == "Mention" end + + test "it accepts quote posts" do + insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i") + + object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!() + + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "actor" => "https://misskey.io/users/7rkrarq81i", + "object" => object + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + # Object was created in the database + object = Object.normalize(activity) + assert object.data["quoteUrl"] == "https://misskey.io/notes/8vs6wxufd0" + + # It fetched the quoted post + assert Object.normalize("https://misskey.io/notes/8vs6wxufd0") + end end describe "prepare outgoing" do diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index b0cf613ac..78a367024 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1380,6 +1380,15 @@ defmodule HttpRequestMock do }} end + def get("https://misskey.io/users/83ssedkv53", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/aimu@misskey.io.json"), + headers: activitypub_object_headers() + }} + end + def get("https://gleasonator.com/users/macgirvin", _, _, _) do {:ok, %Tesla.Env{ @@ -1446,6 +1455,15 @@ defmodule HttpRequestMock do }} end + def get("https://misskey.io/notes/8vs6wxufd0", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json"), + headers: activitypub_object_headers() + }} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} From cc4badaf60462fdb8bb57225437e3dd360ee0dfb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 19:14:39 -0600 Subject: [PATCH 0334/1008] Transmogrifier: fix quoteUrl here too --- .../web/activity_pub/transmogrifier.ex | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index c466271ca..f5771e75e 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -166,9 +166,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_in_reply_to(object, _options), do: object - def fix_quote(object, options \\ []) + def fix_quote_url(object, options \\ []) - def fix_quote(%{"quoteUrl" => quote_url} = object, options) + def fix_quote_url(%{"quoteUrl" => quote_url} = object, options) when not is_nil(quote_url) do with {:ok, quoted_object} <- get_obj_helper(quote_url, options), %Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do @@ -180,7 +180,22 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - def fix_quote(object, _options), do: object + # Fix for Fedibird + # https://github.com/fedibird/mastodon/issues/9 + def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do + object + |> Map.put("quoteUrl", quote_url) + |> fix_quote_url(options) + end + + # Misskey fallback + def fix_quote_url(%{"_misskey_quote" => quote_url} = object, options) do + object + |> Map.put("quoteUrl", quote_url) + |> fix_quote_url(options) + end + + def fix_quote_url(object, _options), do: object defp prepare_in_reply_to(in_reply_to) do cond do @@ -470,7 +485,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> strip_internal_fields() |> fix_type(fetch_options) |> fix_in_reply_to(fetch_options) - |> fix_quote(fetch_options) + |> fix_quote_url(fetch_options) data = Map.put(data, "object", object) options = Keyword.put(options, :local, false) From ce5eb3172321f0ef2ae85d7819b44cc8241a5581 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 19:47:08 -0600 Subject: [PATCH 0335/1008] StatusView: show quoted posts through the API, probably --- .../web/mastodon_api/views/status_view.ex | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index dea22f9c2..b966a84d0 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -57,6 +57,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end) end + defp get_quoted_activities([]), do: %{} + + defp get_quoted_activities(activities) do + activities + |> Enum.map(fn + %{data: %{"type" => "Create"}} = activity -> + object = Object.normalize(activity, fetch: false) + object && object.data["quoteUrl"] != "" && object.data["quoteUrl"] + + _ -> + nil + end) + |> Enum.filter(& &1) + |> Activity.create_by_object_ap_id_with_object() + |> Repo.all() + |> Enum.reduce(%{}, fn activity, acc -> + object = Object.normalize(activity, fetch: false) + if object, do: Map.put(acc, object.data["id"], activity), else: acc + end) + end + # DEPRECATED This field seems to be a left-over from the StatusNet era. # If your application uses `pleroma.conversation_id`: this field is deprecated. # It is currently stubbed instead by doing a CRC32 of the context, and @@ -97,6 +118,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # length(activities_with_links) * timeout fetch_rich_media_for_activities(activities) replied_to_activities = get_replied_to_activities(activities) + quoted_activities = get_quoted_activities(activities) parent_activities = activities @@ -129,6 +151,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do opts = opts |> Map.put(:replied_to_activities, replied_to_activities) + |> Map.put(:quoted_activities, quoted_activities) |> Map.put(:parent_activities, parent_activities) |> Map.put(:relationships, relationships_opt) @@ -277,7 +300,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end reply_to = get_reply_to(activity, opts) - reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"]) history_len = @@ -290,6 +312,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # Here the implicit index of the current content is 0 chrono_order = history_len - 1 + quote_activity = get_quote(activity, opts) + content = object |> render_content() @@ -398,6 +422,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do conversation_id: get_context_id(activity), context: object.data["context"], in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, + quote_id: quote_activity && to_string(quote_activity.id), content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary}, expires_at: expires_at, @@ -633,6 +658,21 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end + def get_quote(activity, %{quoted_activities: quoted_activities}) do + object = Object.normalize(activity, fetch: false) + quoted_activities[object.data["quoteUrl"]] + end + + def get_quote(%{data: %{"object" => _object}} = activity, _) do + object = Object.normalize(activity, fetch: false) + + if object.data["quoteUrl"] && object.data["quoteUrl"] != "" do + Activity.get_create_by_object_ap_id(object.data["quoteUrl"]) + else + nil + end + end + def render_content(%{data: %{"name" => name}} = object) when not is_nil(name) and name != "" do url = object.data["url"] || object.data["id"] From 0d9c443e51c85d9ded3e20954c9620f7a9d2361e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 20:05:58 -0600 Subject: [PATCH 0336/1008] StatusView: render the whole quoted status --- lib/pleroma/web/api_spec/schemas/status.ex | 5 +++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 10 +++++++++- .../web/mastodon_api/views/status_view_test.exs | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index bc29cf4a6..39241aa39 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -193,6 +193,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do nullable: true, description: "The `acct` property of User entity for replied user (if any)" }, + quote: %Schema{ + allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}], + nullable: true, + description: "Quoted status (if any)" + }, local: %Schema{ type: :boolean, description: "`true` if the post was made on the local instance" diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index b966a84d0..5bde1ce04 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -314,6 +314,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do quote_activity = get_quote(activity, opts) + quote_post = + if quote_activity do + quote_rendering_opts = Map.put(opts, :activity, quote_activity) + render("show.json", quote_rendering_opts) + else + nil + end + content = object |> render_content() @@ -422,7 +430,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do conversation_id: get_context_id(activity), context: object.data["context"], in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, - quote_id: quote_activity && to_string(quote_activity.id), + quote: quote_post, content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary}, expires_at: expires_at, diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index b93335190..b10b0f0b9 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -326,6 +326,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do conversation_id: convo_id, context: object_data["context"], in_reply_to_account_acct: nil, + quote: nil, content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])}, expires_at: nil, From 6ac19c3999c543e5a26bbf04932a6a7aaa447b99 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 21:27:05 -0600 Subject: [PATCH 0337/1008] ActivityDraft: create quote posts --- lib/pleroma/web/common_api/activity_draft.ex | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 63ed48a27..375aabc91 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -22,6 +22,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do attachments: [], in_reply_to: nil, in_reply_to_conversation: nil, + quote_post: nil, visibility: nil, expires_at: nil, extra: nil, @@ -53,6 +54,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> poll() |> with_valid(&in_reply_to/1) |> with_valid(&in_reply_to_conversation/1) + |> with_valid("e_post/1) |> with_valid(&visibility/1) |> content() |> with_valid(&to_and_cc/1) @@ -132,6 +134,18 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp in_reply_to(draft), do: draft + defp quote_post(%{params: %{quote_id: ""}} = draft), do: draft + + defp quote_post(%{params: %{quote_id: id}} = draft) when is_binary(id) do + %__MODULE__{draft | quote_post: Activity.get_by_id(id)} + end + + defp quote_post(%{params: %{quote_id: %Activity{} = quote_post}} = draft) do + %__MODULE__{draft | quote_post: quote_post} + end + + defp quote_post(draft), do: draft + defp in_reply_to_conversation(draft) do in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id]) %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} From d4fea8b5595e9e6cd37bdb1cee21285f905693f1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 22:15:54 -0600 Subject: [PATCH 0338/1008] ActivityDraft: allow quoting --- lib/pleroma/web/activity_pub/builder.ex | 11 +++++++++++ .../web/api_spec/operations/status_operation.ex | 7 ++++++- test/pleroma/web/common_api_test.exs | 12 ++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 8eab3a241..eb0bb0e33 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -217,6 +217,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do "tag" => Keyword.values(draft.tags) |> Enum.uniq() } |> add_in_reply_to(draft.in_reply_to) + |> add_quote(draft.quote_post) |> Map.merge(draft.extra) {:ok, data, []} @@ -232,6 +233,16 @@ defmodule Pleroma.Web.ActivityPub.Builder do end end + defp add_quote(object, nil), do: object + + defp add_quote(object, quote_post) do + with %Object{} = quote_object <- Object.normalize(quote_post, fetch: false) do + Map.put(object, "quoteUrl", quote_object.data["id"]) + else + _ -> object + end + end + def chat_message(actor, recipient, content, opts \\ []) do basic = %{ "id" => Utils.generate_object_id(), diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 5d6e82f3c..8fa3b0890 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -581,7 +581,12 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do type: :string, description: "Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`." - } + }, + quote_id: %Schema{ + nullable: true, + allOf: [FlakeID], + description: "ID of the status being quoted, if any" + }, }, example: %{ "status" => "What time is it?", diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 0d76d6581..09df27acb 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -796,6 +796,18 @@ defmodule Pleroma.Web.CommonAPITest do scheduled_at: expires_at ) end + + test "it allows allows quote posting" do + user = insert(:user) + + {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"}) + {:ok, quote_post} = CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id}) + + quoted = Object.normalize(quoted) + quote_post = Object.normalize(quote_post) + + assert quote_post.data["quoteUrl"] == quoted.data["id"] + end end describe "reactions" do From c20e90e898affc9d00d8b7d6b71f11157f5c7837 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 22:29:13 -0600 Subject: [PATCH 0339/1008] BuilderTest: build quote post --- .../pleroma/web/activity_pub/builder_test.exs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/pleroma/web/activity_pub/builder_test.exs b/test/pleroma/web/activity_pub/builder_test.exs index eb175a1be..52058a0a3 100644 --- a/test/pleroma/web/activity_pub/builder_test.exs +++ b/test/pleroma/web/activity_pub/builder_test.exs @@ -44,5 +44,34 @@ defmodule Pleroma.Web.ActivityPub.BuilderTest do assert {:ok, ^expected, []} = Builder.note(draft) end + + test "quote post" do + user = insert(:user) + note = insert(:note) + + draft = %ActivityDraft{ + user: user, + context: "2hu", + content_html: "

This is :moominmamma: note

", + quote_post: note, + extra: %{} + } + + expected = %{ + "actor" => user.ap_id, + "attachment" => [], + "content" => "

This is :moominmamma: note

", + "context" => "2hu", + "sensitive" => false, + "type" => "Note", + "quoteUrl" => note.data["id"], + "cc" => [], + "summary" => nil, + "tag" => [], + "to" => [] + } + + assert {:ok, ^expected, []} = Builder.note(draft) + end end end From 3a8b5d90df6de502debaee4d670211bcf64ad1db Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 22:35:08 -0600 Subject: [PATCH 0340/1008] StatusControllerTest: test creating a quote post --- .../controllers/status_controller_test.exs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 76c289ee7..ba49256b5 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -125,6 +125,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do ) end + test "posting a quote post", %{conn: conn} do + user = insert(:user) + + {:ok, %{id: activity_id}} = CommonAPI.post(user, %{status: "yolo"}) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses", %{ + "status" => "indeed", + "quote_id" => activity_id + }) + + assert %{"id" => id, "pleroma" => %{"quote" => %{"id" => ^activity_id}}} = + json_response_and_validate_schema(conn, 200) + + assert Activity.get_by_id(id) + end + test "it fails to create a status if `expires_in` is less or equal than an hour", %{ conn: conn } do From cbd1760efac872c00edad15f352ffe4d2e0e1e12 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 22:41:57 -0600 Subject: [PATCH 0341/1008] TransmogrifierTest: prepare an outgoing quote post --- .../pleroma/web/activity_pub/transmogrifier_test.exs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 2c8e5ba21..824398e38 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -372,6 +372,18 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do } } = prepared["object"] end + + test "it prepares a quote post" do + user = insert(:user) + + {:ok, quoted_post} = CommonAPI.post(user, %{status: "hey"}) + {:ok, quote_post} = CommonAPI.post(user, %{status: "hey", quote_id: quoted_post.id}) + + {:ok, modified} = Transmogrifier.prepare_outgoing(quote_post.data) + + quoted_post = Object.normalize(quoted_post) + assert modified["object"]["quoteUrl"] == quoted_post.data["id"] + end end describe "actor rewriting" do From 96009739173e5e48a636bb964855eb7aea11c828 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 22:57:42 -0600 Subject: [PATCH 0342/1008] mix format --- lib/pleroma/web/api_spec/operations/status_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 8fa3b0890..c133a3aac 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -586,7 +586,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do nullable: true, allOf: [FlakeID], description: "ID of the status being quoted, if any" - }, + } }, example: %{ "status" => "What time is it?", From f4ccdfd5033e7b1136ae0fe4e41dba78d83e80cf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 23:02:44 -0600 Subject: [PATCH 0343/1008] Fix typos --- .../article_note_page_validator_test.exs | 12 ++++++------ test/pleroma/web/common_api_test.exs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index c3cde00b5..dec2e28c9 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -121,19 +121,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest insert(:user, ap_id: "https://fedibird.com/users/noellabo") data = File.read!("test/fixtures/quote_post/fedibird_quote_post.json") |> Jason.decode!() - chg = ArticleNotePageValidator.cast_and_validate(data) + cng = ArticleNotePageValidator.cast_and_validate(data) - assert chg.valid? - assert chg.changes.quoteUrl == "https://misskey.io/notes/8vsn2izjwh" + assert cng.valid? + assert cng.changes.quoteUrl == "https://misskey.io/notes/8vsn2izjwh" end test "Misskey quote post" do insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i") data = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!() - chg = ArticleNotePageValidator.cast_and_validate(data) + cng = ArticleNotePageValidator.cast_and_validate(data) - assert chg.valid? - assert chg.changes.quoteUrl == "https://misskey.io/notes/8vs6wxufd0" + assert cng.valid? + assert cng.changes.quoteUrl == "https://misskey.io/notes/8vs6wxufd0" end end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 09df27acb..734e6dd82 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -797,7 +797,7 @@ defmodule Pleroma.Web.CommonAPITest do ) end - test "it allows allows quote posting" do + test "it allows quote posting" do user = insert(:user) {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"}) From 5716f88a1d8424cf7c62a0491b3bf9607dc9aa3f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 23:09:33 -0600 Subject: [PATCH 0344/1008] InstanceView: add "quote_posting" feature --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index efd2a0af6..1b01d7371 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -69,6 +69,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "multifetch", "pleroma:api/v1/notifications:include_types_filter", "editing", + "quote_posting", if Config.get([:activitypub, :blockers_visible]) do "blockers_visible" end, From db46abce477b83b252e0ad87f74ac9266e9cb118 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 22 Jan 2022 23:29:55 -0600 Subject: [PATCH 0345/1008] @context: add quoteUrl --- 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 650118475..5d8244a11 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -26,6 +26,7 @@ "@id": "litepub:listMessage", "@type": "@id" }, + "quoteUrl": "as:quoteUrl", "oauthRegistrationEndpoint": { "@id": "litepub:oauthRegistrationEndpoint", "@type": "@id" From 80ab2572a4d5590d738cc763a87156b3f79362fb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 23 Jan 2022 13:55:25 -0600 Subject: [PATCH 0346/1008] Return quote_url through the API, don't render quotes more than 1 level deep --- lib/pleroma/web/api_spec/schemas/status.ex | 6 +++++ .../web/mastodon_api/views/status_view.ex | 7 +++++- .../controllers/status_controller_test.exs | 9 +++++--- .../mastodon_api/views/status_view_test.exs | 23 +++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 39241aa39..f4ee9b38c 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -198,6 +198,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do nullable: true, description: "Quoted status (if any)" }, + quote_url: %Schema{ + type: :string, + format: :uri, + nullable: true, + description: "URL of the quoted status" + }, local: %Schema{ type: :boolean, description: "`true` if the post was made on the local instance" diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 5bde1ce04..06adfb221 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -316,7 +316,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do quote_post = if quote_activity do - quote_rendering_opts = Map.put(opts, :activity, quote_activity) + quote_rendering_opts = Map.merge(opts, %{activity: quote_activity, show_quote: false}) render("show.json", quote_rendering_opts) else nil @@ -431,6 +431,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do context: object.data["context"], in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, quote: quote_post, + quote_url: object.data["quoteUrl"], content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary}, expires_at: expires_at, @@ -666,6 +667,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end + def get_quote(_activity, %{show_quote: false}) do + nil + end + def get_quote(activity, %{quoted_activities: quoted_activities}) do object = Object.normalize(activity, fetch: false) quoted_activities[object.data["quoteUrl"]] diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index ba49256b5..de3b52e26 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -128,7 +128,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "posting a quote post", %{conn: conn} do user = insert(:user) - {:ok, %{id: activity_id}} = CommonAPI.post(user, %{status: "yolo"}) + {:ok, %{id: activity_id} = activity} = CommonAPI.post(user, %{status: "yolo"}) + %{data: %{"id" => quote_url}} = Object.normalize(activity) conn = conn @@ -138,8 +139,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do "quote_id" => activity_id }) - assert %{"id" => id, "pleroma" => %{"quote" => %{"id" => ^activity_id}}} = - json_response_and_validate_schema(conn, 200) + assert %{ + "id" => id, + "pleroma" => %{"quote" => %{"id" => ^activity_id}, "quote_url" => ^quote_url} + } = json_response_and_validate_schema(conn, 200) assert Activity.get_by_id(id) end diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index b10b0f0b9..f50b02799 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -327,6 +327,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do context: object_data["context"], in_reply_to_account_acct: nil, quote: nil, + quote_url: nil, content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])}, expires_at: nil, @@ -423,6 +424,28 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert status.in_reply_to_id == to_string(note.id) end + test "a quote post" do + post = insert(:note_activity) + user = insert(:user) + + {:ok, quote_post} = CommonAPI.post(user, %{status: "he", quote_id: post.id}) + {:ok, quoted_quote_post} = CommonAPI.post(user, %{status: "yo", quote_id: quote_post.id}) + + status = StatusView.render("show.json", %{activity: quoted_quote_post}) + + assert status.pleroma.quote.id == to_string(quote_post.id) + assert status.pleroma.quote_url == Object.normalize(quote_post).data["id"] + + # Quotes don't go more than one level deep + refute status.pleroma.quote.pleroma.quote + assert status.pleroma.quote.pleroma.quote_url == Object.normalize(post).data["id"] + + # In an index + [status] = StatusView.render("index.json", %{activities: [quoted_quote_post], as: :activity}) + + assert status.pleroma.quote.id == to_string(quote_post.id) + end + test "contains mentions" do user = insert(:user) mentioned = insert(:user) From 54a989793878c63900d2c6de7b4ffc8fbd8fe8c6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 23 Jan 2022 15:46:44 -0600 Subject: [PATCH 0347/1008] ActivityDraft: mention the OP of a quoted post --- lib/pleroma/web/common_api/activity_draft.ex | 19 +++++++++++-------- test/pleroma/web/common_api_test.exs | 12 ++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 375aabc91..588aba55e 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -137,11 +137,11 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp quote_post(%{params: %{quote_id: ""}} = draft), do: draft defp quote_post(%{params: %{quote_id: id}} = draft) when is_binary(id) do - %__MODULE__{draft | quote_post: Activity.get_by_id(id)} - end - - defp quote_post(%{params: %{quote_id: %Activity{} = quote_post}} = draft) do - %__MODULE__{draft | quote_post: quote_post} + with %Activity{actor: actor_ap_id} = activity <- Activity.get_by_id(id) do + %__MODULE__{draft | quote_post: activity, mentions: [actor_ap_id]} + else + _ -> draft + end end defp quote_post(draft), do: draft @@ -178,12 +178,15 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end - defp content(draft) do + defp content(%{mentions: mentions} = draft) do {content_html, mentioned_users, tags} = Utils.make_content_html(draft) + mentioned_ap_ids = + Enum.map(mentioned_users, fn {_, mentioned_user} -> mentioned_user.ap_id end) + mentions = - mentioned_users - |> Enum.map(fn {_, mentioned_user} -> mentioned_user.ap_id end) + mentions + |> Kernel.++(mentioned_ap_ids) |> Utils.get_addressed_users(draft.params[:to]) %__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags} diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 734e6dd82..051e770d7 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -807,6 +807,18 @@ defmodule Pleroma.Web.CommonAPITest do quote_post = Object.normalize(quote_post) assert quote_post.data["quoteUrl"] == quoted.data["id"] + + # The OP is mentioned + assert quoted.data["actor"] in quote_post.data["to"] + end + + test "quote posting with explicit addressing doesn't mention the OP" do + user = insert(:user) + + {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"}) + {:ok, quote_post} = CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id, to: []}) + + assert Object.normalize(quote_post).data["to"] == [Pleroma.Constants.as_public()] end end From 1f19dd76f66ca657ddfe79a51e59b8997a4c6321 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 23 Jan 2022 16:03:46 -0600 Subject: [PATCH 0348/1008] ActivityDraft: mix format, defensive actor ID --- lib/pleroma/web/common_api/activity_draft.ex | 16 ++++++++++------ test/pleroma/web/common_api_test.exs | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 588aba55e..95534f3cb 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do alias Pleroma.Web.CommonAPI.Utils import Pleroma.Web.Gettext + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] defstruct valid?: true, errors: [], @@ -134,13 +135,16 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp in_reply_to(draft), do: draft - defp quote_post(%{params: %{quote_id: ""}} = draft), do: draft + defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do + case Activity.get_by_id(id) do + %Activity{actor: actor_ap_id} = activity when not_empty_string(actor_ap_id) -> + %__MODULE__{draft | quote_post: activity, mentions: [actor_ap_id]} - defp quote_post(%{params: %{quote_id: id}} = draft) when is_binary(id) do - with %Activity{actor: actor_ap_id} = activity <- Activity.get_by_id(id) do - %__MODULE__{draft | quote_post: activity, mentions: [actor_ap_id]} - else - _ -> draft + %Activity{} = activity -> + %__MODULE__{draft | quote_post: activity} + + _ -> + draft end end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 051e770d7..960d0cf16 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -816,7 +816,9 @@ defmodule Pleroma.Web.CommonAPITest do user = insert(:user) {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"}) - {:ok, quote_post} = CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id, to: []}) + + {:ok, quote_post} = + CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id, to: []}) assert Object.normalize(quote_post).data["to"] == [Pleroma.Constants.as_public()] end From 36a5578d2b16ba9f771fff55daafa85ec606a6be Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 24 Jan 2022 15:34:23 -0600 Subject: [PATCH 0349/1008] Scrubber.Default: allow span.quote-inline for quote post compatibility --- priv/scrubbers/default.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index d1215d2e0..56324a9fa 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -60,7 +60,7 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes(:u, ["lang"]) Meta.allow_tag_with_these_attributes(:ul, ["lang"]) - Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "recipients-inline"]) + Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "recipients-inline", "quote-inline"]) Meta.allow_tag_with_these_attributes(:span, ["lang"]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"]) From 57ef1d121101d785c043ef6aaf2d33bb9be3ec3b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 24 Jan 2022 16:44:35 -0600 Subject: [PATCH 0350/1008] Add InlineQuotePolicy to force quote URLs inline --- config/config.exs | 2 + docs/configuration/cheatsheet.md | 4 ++ .../activity_pub/mrf/inline_quote_policy.ex | 53 ++++++++++++++++++ .../mrf/inline_quote_policy_test.exs | 56 +++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex create mode 100644 test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs diff --git a/config/config.exs b/config/config.exs index ebcbf8b49..56cc34db5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -434,6 +434,8 @@ config :pleroma, :mrf_object_age, config :pleroma, :mrf_follow_bot, follower_nickname: nil +config :pleroma, :mrf_inline_quote, prefix: "RT" + config :pleroma, :rich_media, enabled: true, ignore_hosts: [], diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index f43cde114..32cc5811a 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -160,6 +160,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content. + * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline. * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. @@ -267,6 +268,9 @@ Notes: * `federated_timeline_removal_url`: A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). * `federated_timeline_removal_shortcode`: A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). +#### :mrf_inline_quote +* `prefix`: Prefix before the link (default: `RT`) + ### :activitypub * `unfollow_blocked`: Whether blocks result in people getting unfollowed * `outgoing_blocks`: Whether to federate blocks to other instances diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex new file mode 100644 index 000000000..0f1dc9f42 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -0,0 +1,53 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do + @moduledoc "Force a quote line into the message content." + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + defp build_inline_quote(prefix, url) do + "

#{prefix}: #{url}
" + end + + defp filter_object(%{"quoteUrl" => quote_url} = object) do + content = object["content"] || "" + + if content =~ quote_url do + object + else + prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix]) + content = content <> build_inline_quote(prefix, quote_url) + Map.put(object, "content", content) + end + end + + @impl true + def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do + {:ok, Map.put(activity, "object", filter_object(object))} + end + + @impl true + def filter(object), do: {:ok, object} + + @impl true + def describe, do: {:ok, %{}} + + @impl true + def config_description do + %{ + key: :mrf_inline_quote, + related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy", + label: "MRF Inline Quote", + description: "Force quote post URLs inline", + children: [ + %{ + key: :prefix, + type: :string, + description: "Prefix before the link", + suggestions: ["RT", "QT", "RE", "RN"] + } + ] + } + end +end diff --git a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs new file mode 100644 index 000000000..81dc06dda --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do + alias Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy + use Pleroma.DataCase + + test "adds quote URL to post content" do + quote_url = "https://gleasonator.com/objects/1234" + + activity = %{ + "type" => "Create", + "actor" => "https://gleasonator.com/users/alex", + "object" => %{ + "type" => "Note", + "content" => "

Nice post

", + "quoteUrl" => quote_url + } + } + + {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) + + assert filtered == + "

Nice post



RT: https://gleasonator.com/objects/1234
" + end + + test "ignores Misskey quote posts" do + object = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!() + + activity = %{ + "type" => "Create", + "actor" => "https://misskey.io/users/7rkrarq81i", + "object" => object + } + + {:ok, filtered} = InlineQuotePolicy.filter(activity) + assert filtered == activity + end + + test "ignores Fedibird quote posts" do + object = File.read!("test/fixtures/quote_post/fedibird_quote_post.json") |> Jason.decode!() + + # Normally the ObjectValidator will fix this before it reaches MRF + object = Map.put(object, "quoteUrl", object["quoteURL"]) + + activity = %{ + "type" => "Create", + "actor" => "https://fedibird.com/users/noellabo", + "object" => object + } + + {:ok, filtered} = InlineQuotePolicy.filter(activity) + assert filtered == activity + end +end From 59326247aa754991add9170e204257a8bf94c40f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 26 Jan 2022 11:21:49 -0600 Subject: [PATCH 0351/1008] CommonAPI: disallow quoting private posts through the API --- lib/pleroma/web/common_api/activity_draft.ex | 15 ++++++++++- .../web/common_api/activity_draft_test.exs | 26 +++++++++++++++++++ test/pleroma/web/common_api_test.exs | 14 ++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 test/pleroma/web/common_api/activity_draft_test.exs diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 95534f3cb..d4875765c 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do alias Pleroma.Conversation.Participation alias Pleroma.Object alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils @@ -57,6 +58,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> with_valid(&in_reply_to_conversation/1) |> with_valid("e_post/1) |> with_valid(&visibility/1) + |> with_valid("ing_visibility/1) |> content() |> with_valid(&to_and_cc/1) |> with_valid(&context/1) @@ -136,7 +138,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp in_reply_to(draft), do: draft defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do - case Activity.get_by_id(id) do + case Activity.get_by_id_with_object(id) do %Activity{actor: actor_ap_id} = activity when not_empty_string(actor_ap_id) -> %__MODULE__{draft | quote_post: activity, mentions: [actor_ap_id]} @@ -165,6 +167,17 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end + defp quoting_visibility(%{quote_post: %Activity{}} = draft) do + with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false), + visibility when visibility in ~w(public unlisted) <- Visibility.get_visibility(object) do + draft + else + _ -> add_error(draft, dgettext("errors", "Cannot quote private message")) + end + end + + defp quoting_visibility(draft), do: draft + defp expires_at(draft) do case CommonAPI.check_expiry_date(draft.params[:expires_in]) do {:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at} diff --git a/test/pleroma/web/common_api/activity_draft_test.exs b/test/pleroma/web/common_api/activity_draft_test.exs new file mode 100644 index 000000000..8a09fc710 --- /dev/null +++ b/test/pleroma/web/common_api/activity_draft_test.exs @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.CommonAPI.ActivityDraftTest do + use Pleroma.DataCase + + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.CommonAPI.ActivityDraft + + import Pleroma.Factory + + test "create/2 with a quote post" do + user = insert(:user) + + {:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) + {:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"}) + {:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) + {:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"}) + + {:error, _} = ActivityDraft.create(user, %{status: "nice", quote_id: direct.id}) + {:error, _} = ActivityDraft.create(user, %{status: "nice", quote_id: private.id}) + {:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: unlisted.id}) + {:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: public.id}) + end +end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 960d0cf16..c4eba8b9c 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -822,6 +822,20 @@ defmodule Pleroma.Web.CommonAPITest do assert Object.normalize(quote_post).data["to"] == [Pleroma.Constants.as_public()] end + + test "quote posting visibility" do + user = insert(:user) + + {:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) + {:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"}) + {:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) + {:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"}) + + {:error, _} = CommonAPI.post(user, %{status: "nice", quote_id: direct.id}) + {:error, _} = CommonAPI.post(user, %{status: "nice", quote_id: private.id}) + {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: unlisted.id}) + {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: public.id}) + end end describe "reactions" do From 6f11f11519f9c735f6b059c250f4bf01e09b305f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 26 Jan 2022 11:49:31 -0600 Subject: [PATCH 0352/1008] StatusView: fix quote visibility --- .../web/mastodon_api/views/status_view.ex | 2 +- .../mastodon_api/views/status_view_test.exs | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 06adfb221..7360d1093 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -315,7 +315,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do quote_activity = get_quote(activity, opts) quote_post = - if quote_activity do + if visible_for_user?(quote_activity, opts[:for]) do quote_rendering_opts = Map.merge(opts, %{activity: quote_activity, show_quote: false}) render("show.json", quote_rendering_opts) else diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index f50b02799..f41ef580d 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -446,6 +446,47 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert status.pleroma.quote.id == to_string(quote_post.id) end + test "quoted private post" do + user = insert(:user) + + # Insert a private post + private = insert(:followers_only_note_activity, user: user) + private_object = Object.normalize(private) + + # Create a public post quoting the private post + quote_private = + insert(:note_activity, note: insert(:note, data: %{"quoteUrl" => private_object.data["id"]})) + + status = StatusView.render("show.json", %{activity: quote_private}) + + # The quote isn't rendered + refute status.pleroma.quote + assert status.pleroma.quote_url == private_object.data["id"] + + # After following the user, the quote is rendered + follower = insert(:user) + CommonAPI.follow(follower, user) + + status = StatusView.render("show.json", %{activity: quote_private, for: follower}) + assert status.pleroma.quote.id == to_string(private.id) + end + + test "quoted direct message" do + # Insert a direct message + direct = insert(:direct_note_activity) + direct_object = Object.normalize(direct) + + # Create a public post quoting the direct message + quote_direct = + insert(:note_activity, note: insert(:note, data: %{"quoteUrl" => direct_object.data["id"]})) + + status = StatusView.render("show.json", %{activity: quote_direct}) + + # The quote isn't rendered + refute status.pleroma.quote + assert status.pleroma.quote_url == direct_object.data["id"] + end + test "contains mentions" do user = insert(:user) mentioned = insert(:user) From 74e0a4555f583a6962ad116bf6e54f06e42fe465 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 26 Jan 2022 11:52:50 -0600 Subject: [PATCH 0353/1008] StatusView: add `quote_visible` param --- lib/pleroma/web/api_spec/schemas/status.ex | 4 ++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 1 + test/pleroma/web/mastodon_api/views/status_view_test.exs | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index f4ee9b38c..5d0eedb08 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -204,6 +204,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do nullable: true, description: "URL of the quoted status" }, + quote_visible: %Schema{ + type: :boolean, + description: "`true` if the quoted post is visible to the user" + }, local: %Schema{ type: :boolean, description: "`true` if the post was made on the local instance" diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 7360d1093..2aa44b0f6 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -432,6 +432,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, quote: quote_post, quote_url: object.data["quoteUrl"], + quote_visible: visible_for_user?(quote_activity, opts[:for]), content: %{"text/plain" => content_plaintext}, spoiler_text: %{"text/plain" => summary}, expires_at: expires_at, diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index f41ef580d..ed0a87558 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -328,6 +328,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do in_reply_to_account_acct: nil, quote: nil, quote_url: nil, + quote_visible: false, content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])}, expires_at: nil, @@ -462,6 +463,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do # The quote isn't rendered refute status.pleroma.quote assert status.pleroma.quote_url == private_object.data["id"] + refute status.pleroma.quote_visible # After following the user, the quote is rendered follower = insert(:user) @@ -469,6 +471,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status = StatusView.render("show.json", %{activity: quote_private, for: follower}) assert status.pleroma.quote.id == to_string(private.id) + assert status.pleroma.quote_visible end test "quoted direct message" do @@ -485,6 +488,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do # The quote isn't rendered refute status.pleroma.quote assert status.pleroma.quote_url == direct_object.data["id"] + refute status.pleroma.quote_visible end test "contains mentions" do From bee7e419597615ac6852942fe563166feba3fe73 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Jan 2022 14:28:06 -0600 Subject: [PATCH 0354/1008] InlineQuotePolicy: don't add line breaks to markdown posts --- .../activity_pub/mrf/inline_quote_policy.ex | 12 ++++++++--- .../mrf/inline_quote_policy_test.exs | 21 ++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex index 0f1dc9f42..46013fc5e 100644 --- a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do @moduledoc "Force a quote line into the message content." @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp build_inline_quote(prefix, url) do - "

#{prefix}: #{url}
" + defp build_inline_quote(prefix, url, br) do + "#{String.duplicate("
", br)}#{prefix}: #{url}
" end defp filter_object(%{"quoteUrl" => quote_url} = object) do @@ -17,7 +17,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do object else prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix]) - content = content <> build_inline_quote(prefix, quote_url) + + inline_quote = + if String.ends_with?(content, "

"), + do: build_inline_quote(prefix, quote_url, 0), + else: build_inline_quote(prefix, quote_url, 2) + + content = content <> inline_quote Map.put(object, "content", content) end end diff --git a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs index 81dc06dda..8e75aaaab 100644 --- a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs @@ -9,6 +9,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do test "adds quote URL to post content" do quote_url = "https://gleasonator.com/objects/1234" + activity = %{ + "type" => "Create", + "actor" => "https://gleasonator.com/users/alex", + "object" => %{ + "type" => "Note", + "content" => "Nice post", + "quoteUrl" => quote_url + } + } + + {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) + + assert filtered == + "Nice post

RT: https://gleasonator.com/objects/1234
" + end + + test "doesn't add line breaks to markdown posts" do + quote_url = "https://gleasonator.com/objects/1234" + activity = %{ "type" => "Create", "actor" => "https://gleasonator.com/users/alex", @@ -22,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) assert filtered == - "

Nice post



RT: https://gleasonator.com/objects/1234
" + "

Nice post

RT: https://gleasonator.com/objects/1234" end test "ignores Misskey quote posts" do From 93e4972b50f9bb0a07a7074fbab2aedbdc0cc4eb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 27 Jan 2022 15:01:20 -0600 Subject: [PATCH 0355/1008] Add InlineQuotePolicy as a default MRF --- config/config.exs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 56cc34db5..9149e925a 100644 --- a/config/config.exs +++ b/config/config.exs @@ -860,7 +860,11 @@ config :pleroma, :restrict_unauthenticated, config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false config :pleroma, :mrf, - policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy], + policies: [ + Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, + Pleroma.Web.ActivityPub.MRF.TagPolicy, + Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy + ], transparency: true, transparency_exclusions: [] From cf8e4258830e3f362ab1e54238d622f6b2056502 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 28 Jan 2022 12:33:07 -0600 Subject: [PATCH 0356/1008] StatusView: return quote post inside a reblog --- lib/pleroma/web/mastodon_api/views/status_view.ex | 10 ++++++---- .../web/mastodon_api/views/status_view_test.exs | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 2aa44b0f6..ba4a8f3eb 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -668,13 +668,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - def get_quote(_activity, %{show_quote: false}) do - nil - end + def get_quote(_activity, %{show_quote: false}), do: nil def get_quote(activity, %{quoted_activities: quoted_activities}) do object = Object.normalize(activity, fetch: false) - quoted_activities[object.data["quoteUrl"]] + + with nil <- quoted_activities[object.data["quoteUrl"]] do + # For when a quote post is inside an Announce + Activity.get_create_by_object_ap_id_with_object(object.data["quoteUrl"]) + end end def get_quote(%{data: %{"object" => _object}} = activity, _) do diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index ed0a87558..6d3a72970 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -491,6 +491,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do refute status.pleroma.quote_visible end + test "repost of quote post" do + post = insert(:note_activity) + user = insert(:user) + + {:ok, quote_post} = CommonAPI.post(user, %{status: "he", quote_id: post.id}) + {:ok, repost} = CommonAPI.repeat(quote_post.id, user) + + [status] = StatusView.render("index.json", %{activities: [repost], as: :activity}) + + assert status.reblog.pleroma.quote.id == to_string(post.id) + end + test "contains mentions" do user = insert(:user) mentioned = insert(:user) From 3c8319fe9f7a7c793f8fdc347be2015190981e33 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 28 Jan 2022 14:06:32 -0600 Subject: [PATCH 0357/1008] Transmogrifier: federate quotes with _misskey_quote field --- lib/pleroma/web/activity_pub/transmogrifier.ex | 9 +++++++++ priv/static/schemas/litepub-0.1.jsonld | 2 ++ test/pleroma/web/activity_pub/transmogrifier_test.exs | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index f5771e75e..163ae54fa 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -660,6 +660,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def set_reply_to_uri(obj), do: obj + # Misskey quotes + # Despite being underscored, it's potentially more reliable for interop. + def set_quote_url(%{"quoteUrl" => quote_url} = object) when is_binary(quote_url) do + Map.put(object, "_misskey_quote", quote_url) + end + + def set_quote_url(obj), do: obj + @doc """ Serialized Mastodon-compatible `replies` collection containing _self-replies_. Based on Mastodon's ActivityPub::NoteSerializer#replies. @@ -714,6 +722,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> prepare_attachments |> set_conversation |> set_reply_to_uri + |> set_quote_url |> set_replies |> strip_internal_fields |> strip_internal_tags diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index 5d8244a11..8559e744d 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -17,6 +17,7 @@ "ostatus": "http://ostatus.org#", "schema": "http://schema.org#", "toot": "http://joinmastodon.org/ns#", + "misskey": "https://misskey-hub.net/ns#", "value": "schema:value", "sensitive": "as:sensitive", "litepub": "http://litepub.social/ns#", @@ -27,6 +28,7 @@ "@type": "@id" }, "quoteUrl": "as:quoteUrl", + "_misskey_quote": "misskey:_misskey_quote", "oauthRegistrationEndpoint": { "@id": "litepub:oauthRegistrationEndpoint", "@type": "@id" diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 824398e38..8c7e0a4c9 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -382,7 +382,11 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, modified} = Transmogrifier.prepare_outgoing(quote_post.data) quoted_post = Object.normalize(quoted_post) + assert modified["object"]["quoteUrl"] == quoted_post.data["id"] + + # Add Misskey's quote as a fallback + assert modified["object"]["_misskey_quote"] == quoted_post.data["id"] end end From 817e308c0d9e42dfc39742c554e4c7a8da6f1c50 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 28 Jan 2022 15:55:52 -0600 Subject: [PATCH 0358/1008] Handle Fedibird's new quoteUri field --- .../article_note_page_validator.ex | 8 ++- .../web/activity_pub/transmogrifier.ex | 19 +++++-- priv/static/schemas/litepub-0.1.jsonld | 2 + .../quote_post/fedibird_quote_uri.json | 54 +++++++++++++++++++ .../article_note_page_validator_test.exs | 10 ++++ .../web/activity_pub/transmogrifier_test.exs | 9 ++-- 6 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 test/fixtures/quote_post/fedibird_quote_uri.json diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index 40bb67934..0b435b251 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -78,7 +78,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do defp fix_quote_url(%{"quoteUrl" => _quote_url} = data), do: data - # Fix for Fedibird + # Fedibird + # https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac + defp fix_quote_url(%{"quoteUri" => quote_url} = data) do + Map.put(data, "quoteUrl", quote_url) + end + + # Old Fedibird (bug) # https://github.com/fedibird/mastodon/issues/9 defp fix_quote_url(%{"quoteURL" => quote_url} = data) do Map.put(data, "quoteUrl", quote_url) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 163ae54fa..01e135fc1 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -180,7 +180,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - # Fix for Fedibird + # Fedibird + # https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac + def fix_quote_url(%{"quoteUri" => quote_url} = object, options) do + object + |> Map.put("quoteUrl", quote_url) + |> fix_quote_url(options) + end + + # Old Fedibird (bug) # https://github.com/fedibird/mastodon/issues/9 def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do object @@ -660,10 +668,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def set_reply_to_uri(obj), do: obj - # Misskey quotes - # Despite being underscored, it's potentially more reliable for interop. def set_quote_url(%{"quoteUrl" => quote_url} = object) when is_binary(quote_url) do - Map.put(object, "_misskey_quote", quote_url) + Map.merge(object, %{ + # Fedibird quote + "quoteUri" => quote_url, + # Misskey quote + "_misskey_quote" => quote_url + }) end def set_quote_url(obj), do: obj diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index 8559e744d..3d68e0714 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -18,6 +18,7 @@ "schema": "http://schema.org#", "toot": "http://joinmastodon.org/ns#", "misskey": "https://misskey-hub.net/ns#", + "fedibird": "http://fedibird.com/ns#", "value": "schema:value", "sensitive": "as:sensitive", "litepub": "http://litepub.social/ns#", @@ -28,6 +29,7 @@ "@type": "@id" }, "quoteUrl": "as:quoteUrl", + "quoteUri": "fedibird:quoteUri", "_misskey_quote": "misskey:_misskey_quote", "oauthRegistrationEndpoint": { "@id": "litepub:oauthRegistrationEndpoint", diff --git a/test/fixtures/quote_post/fedibird_quote_uri.json b/test/fixtures/quote_post/fedibird_quote_uri.json new file mode 100644 index 000000000..7c328fdb9 --- /dev/null +++ b/test/fixtures/quote_post/fedibird_quote_uri.json @@ -0,0 +1,54 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + { + "ostatus": "http://ostatus.org#", + "atomUri": "ostatus:atomUri", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "sensitive": "as:sensitive", + "toot": "http://joinmastodon.org/ns#", + "votersCount": "toot:votersCount", + "fedibird": "http://fedibird.com/ns#", + "quoteUri": "fedibird:quoteUri", + "expiry": "fedibird:expiry" + } + ], + "id": "https://fedibird.com/users/noellabo/statuses/107699335988346142", + "type": "Note", + "summary": null, + "inReplyTo": null, + "published": "2022-01-28T09:17:30Z", + "url": "https://fedibird.com/@noellabo/107699335988346142", + "attributedTo": "https://fedibird.com/users/noellabo", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://fedibird.com/users/noellabo/followers" + ], + "sensitive": false, + "atomUri": "https://fedibird.com/users/noellabo/statuses/107699335988346142", + "inReplyToAtomUri": null, + "conversation": "tag:fedibird.com,2022-01-28:objectId=107699335988345290:objectType=Conversation", + "context": "https://fedibird.com/contexts/107699335988345290", + "quoteUri": "https://fedibird.com/users/yamako/statuses/107699333438289729", + "_misskey_quote": "https://fedibird.com/users/yamako/statuses/107699333438289729", + "_misskey_content": "美味しそう", + "content": "

美味しそう
QT: https://fedibird.com/@yamako/107699333438289729

", + "contentMap": { + "ja": "

美味しそう
QT: https://fedibird.com/@yamako/107699333438289729

" + }, + "attachment": [], + "tag": [], + "replies": { + "id": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies", + "type": "Collection", + "first": { + "type": "CollectionPage", + "next": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies?only_other_accounts=true&page=true", + "partOf": "https://fedibird.com/users/noellabo/statuses/107699335988346142/replies", + "items": [] + } + } +} diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index dec2e28c9..a4ba38e6a 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -127,6 +127,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest assert cng.changes.quoteUrl == "https://misskey.io/notes/8vsn2izjwh" end + test "Fedibird quote post with quoteUri field" do + insert(:user, ap_id: "https://fedibird.com/users/noellabo") + + data = File.read!("test/fixtures/quote_post/fedibird_quote_uri.json") |> Jason.decode!() + cng = ArticleNotePageValidator.cast_and_validate(data) + + assert cng.valid? + assert cng.changes.quoteUrl == "https://fedibird.com/users/yamako/statuses/107699333438289729" + end + test "Misskey quote post" do insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i") diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 8c7e0a4c9..1838f96bb 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -381,12 +381,11 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, modified} = Transmogrifier.prepare_outgoing(quote_post.data) - quoted_post = Object.normalize(quoted_post) + %{data: %{"id" => quote_id}} = Object.normalize(quoted_post) - assert modified["object"]["quoteUrl"] == quoted_post.data["id"] - - # Add Misskey's quote as a fallback - assert modified["object"]["_misskey_quote"] == quoted_post.data["id"] + assert modified["object"]["quoteUrl"] == quote_id + assert modified["object"]["quoteUri"] == quote_id + assert modified["object"]["_misskey_quote"] == quote_id end end From 4075eecca0033e4487fa9d5d5bb75384597c3e79 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 28 Jan 2022 16:07:17 -0600 Subject: [PATCH 0359/1008] InlineQuotePolicy: improve the way Markdown quotes are displayed by other software --- .../web/activity_pub/mrf/inline_quote_policy.ex | 13 +++++++------ .../activity_pub/mrf/inline_quote_policy_test.exs | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex index 46013fc5e..7de4935f2 100644 --- a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do @moduledoc "Force a quote line into the message content." @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp build_inline_quote(prefix, url, br) do - "#{String.duplicate("
", br)}#{prefix}: #{url}
" + defp build_inline_quote(prefix, url) do + "

#{prefix}: #{url}
" end defp filter_object(%{"quoteUrl" => quote_url} = object) do @@ -18,12 +18,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do else prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix]) - inline_quote = + content = if String.ends_with?(content, "

"), - do: build_inline_quote(prefix, quote_url, 0), - else: build_inline_quote(prefix, quote_url, 2) + do: + String.trim_trailing(content, "

") <> + build_inline_quote(prefix, quote_url) <> "

", + else: content <> build_inline_quote(prefix, quote_url) - content = content <> inline_quote Map.put(object, "content", content) end end diff --git a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs index 8e75aaaab..2291c1dac 100644 --- a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs @@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) assert filtered == - "Nice post

RT: https://gleasonator.com/objects/1234
" + "Nice post

RT: https://gleasonator.com/objects/1234
" end test "doesn't add line breaks to markdown posts" do @@ -41,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) assert filtered == - "

Nice post

RT: https://gleasonator.com/objects/1234" + "

Nice post

RT: https://gleasonator.com/objects/1234

" end test "ignores Misskey quote posts" do From 79fca39faf6d084eabb6be44a2263431943b8dd4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 28 Jan 2022 17:53:19 -0600 Subject: [PATCH 0360/1008] Actually, don't send _misskey_quote anymore --- lib/pleroma/web/activity_pub/transmogrifier.ex | 11 +++++------ priv/static/schemas/litepub-0.1.jsonld | 2 -- test/pleroma/web/activity_pub/transmogrifier_test.exs | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 01e135fc1..6c6cd712b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -668,13 +668,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def set_reply_to_uri(obj), do: obj + @doc """ + Fedibird compatibility + https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac + """ def set_quote_url(%{"quoteUrl" => quote_url} = object) when is_binary(quote_url) do - Map.merge(object, %{ - # Fedibird quote - "quoteUri" => quote_url, - # Misskey quote - "_misskey_quote" => quote_url - }) + Map.put(object, "quoteUri", quote_url) end def set_quote_url(obj), do: obj diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index 3d68e0714..b499a96f5 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -17,7 +17,6 @@ "ostatus": "http://ostatus.org#", "schema": "http://schema.org#", "toot": "http://joinmastodon.org/ns#", - "misskey": "https://misskey-hub.net/ns#", "fedibird": "http://fedibird.com/ns#", "value": "schema:value", "sensitive": "as:sensitive", @@ -30,7 +29,6 @@ }, "quoteUrl": "as:quoteUrl", "quoteUri": "fedibird:quoteUri", - "_misskey_quote": "misskey:_misskey_quote", "oauthRegistrationEndpoint": { "@id": "litepub:oauthRegistrationEndpoint", "@type": "@id" diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 1838f96bb..4a192cdc0 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -385,7 +385,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert modified["object"]["quoteUrl"] == quote_id assert modified["object"]["quoteUri"] == quote_id - assert modified["object"]["_misskey_quote"] == quote_id end end From f9697e68c2b75a77575b9b7c89d08a5687bfd7b4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 30 Jan 2022 10:57:29 -0600 Subject: [PATCH 0361/1008] InlineQuotePolicy: skip objects which already have an .inline-quote span --- .../activity_pub/mrf/inline_quote_policy.ex | 13 ++++- .../quote_post/fedibird_quote_mismatched.json | 54 +++++++++++++++++++ .../mrf/inline_quote_policy_test.exs | 17 ++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/quote_post/fedibird_quote_mismatched.json diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex index 7de4935f2..c78675caf 100644 --- a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -10,10 +10,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do "

#{prefix}: #{url}
" end + defp has_inline_quote?(content, quote_url) do + cond do + # Does the quote URL exist in the content? + content =~ quote_url -> true + # Does the content already have a .quote-inline span? + content =~ "" -> true + # No inline quote found + true -> false + end + end + defp filter_object(%{"quoteUrl" => quote_url} = object) do content = object["content"] || "" - if content =~ quote_url do + if has_inline_quote?(content, quote_url) do object else prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix]) diff --git a/test/fixtures/quote_post/fedibird_quote_mismatched.json b/test/fixtures/quote_post/fedibird_quote_mismatched.json new file mode 100644 index 000000000..8dee5daff --- /dev/null +++ b/test/fixtures/quote_post/fedibird_quote_mismatched.json @@ -0,0 +1,54 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + { + "ostatus": "http://ostatus.org#", + "atomUri": "ostatus:atomUri", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "sensitive": "as:sensitive", + "toot": "http://joinmastodon.org/ns#", + "votersCount": "toot:votersCount", + "fedibird": "http://fedibird.com/ns#", + "quoteUri": "fedibird:quoteUri", + "expiry": "fedibird:expiry" + } + ], + "id": "https://fedibird.com/users/noellabo/statuses/107712183700212249", + "type": "Note", + "summary": null, + "inReplyTo": null, + "published": "2022-01-30T15:44:50Z", + "url": "https://fedibird.com/@noellabo/107712183700212249", + "attributedTo": "https://fedibird.com/users/noellabo", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://fedibird.com/users/noellabo/followers" + ], + "sensitive": false, + "atomUri": "https://fedibird.com/users/noellabo/statuses/107712183700212249", + "inReplyToAtomUri": null, + "conversation": "tag:fedibird.com,2022-01-30:objectId=107712183700170473:objectType=Conversation", + "context": "https://fedibird.com/contexts/107712183700170473", + "quoteUri": "https://unnerv.jp/users/UN_NERV/statuses/107712176849067434", + "_misskey_quote": "https://unnerv.jp/users/UN_NERV/statuses/107712176849067434", + "_misskey_content": "揺れていたようだ", + "content": "

揺れていたようだ
QT: https://unnerv.jp/@UN_NERV/107712176849067434

", + "contentMap": { + "ja": "

揺れていたようだ
QT: https://unnerv.jp/@UN_NERV/107712176849067434

" + }, + "attachment": [], + "tag": [], + "replies": { + "id": "https://fedibird.com/users/noellabo/statuses/107712183700212249/replies", + "type": "Collection", + "first": { + "type": "CollectionPage", + "next": "https://fedibird.com/users/noellabo/statuses/107712183700212249/replies?only_other_accounts=true&page=true", + "partOf": "https://fedibird.com/users/noellabo/statuses/107712183700212249/replies", + "items": [] + } + } +} diff --git a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs index 2291c1dac..44ee91d4b 100644 --- a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs @@ -72,4 +72,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do {:ok, filtered} = InlineQuotePolicy.filter(activity) assert filtered == activity end + + test "skips objects which already have an .inline-quote span" do + object = + File.read!("test/fixtures/quote_post/fedibird_quote_mismatched.json") |> Jason.decode!() + + # Normally the ObjectValidator will fix this before it reaches MRF + object = Map.put(object, "quoteUrl", object["quoteUri"]) + + activity = %{ + "type" => "Create", + "actor" => "https://fedibird.com/users/noellabo", + "object" => object + } + + {:ok, filtered} = InlineQuotePolicy.filter(activity) + assert filtered == activity + end end From b0a7e795e799d3c8d750ab909657ec7b3d0bfd58 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 10 Jul 2023 17:57:09 -0400 Subject: [PATCH 0362/1008] Unify logic for normalizing quoteUri --- .../article_note_page_validator.ex | 23 +--------- .../object_validators/common_fixes.ex | 21 ++++++++++ .../web/activity_pub/transmogrifier.ex | 42 ++++++------------- priv/scrubbers/default.ex | 7 +++- 4 files changed, 40 insertions(+), 53 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex index 0b435b251..1b5b2e8fb 100644 --- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex @@ -76,27 +76,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do def fix_attachments(data), do: data - defp fix_quote_url(%{"quoteUrl" => _quote_url} = data), do: data - - # Fedibird - # https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac - defp fix_quote_url(%{"quoteUri" => quote_url} = data) do - Map.put(data, "quoteUrl", quote_url) - end - - # Old Fedibird (bug) - # https://github.com/fedibird/mastodon/issues/9 - defp fix_quote_url(%{"quoteURL" => quote_url} = data) do - Map.put(data, "quoteUrl", quote_url) - end - - # Misskey fallback - defp fix_quote_url(%{"_misskey_quote" => quote_url} = data) do - Map.put(data, "quoteUrl", quote_url) - end - - defp fix_quote_url(data), do: data - defp fix(data) do data |> CommonFixes.fix_actor() @@ -105,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do |> fix_tag() |> fix_replies() |> fix_attachments() - |> fix_quote_url() + |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() |> Transmogrifier.fix_content_map() end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index add46d561..cc2ad9116 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -76,4 +76,25 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do Map.put(data, "to", to) end + + def fix_quote_url(%{"quoteUrl" => _quote_url} = data), do: data + + # Fedibird + # https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac + def fix_quote_url(%{"quoteUri" => quote_url} = data) do + Map.put(data, "quoteUrl", quote_url) + end + + # Old Fedibird (bug) + # https://github.com/fedibird/mastodon/issues/9 + def fix_quote_url(%{"quoteURL" => quote_url} = data) do + Map.put(data, "quoteUrl", quote_url) + end + + # Misskey fallback + def fix_quote_url(%{"_misskey_quote" => quote_url} = data) do + Map.put(data, "quoteUrl", quote_url) + end + + def fix_quote_url(data), do: data end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 6c6cd712b..86d3ac60f 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -166,45 +166,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_in_reply_to(object, _options), do: object - def fix_quote_url(object, options \\ []) + def fix_quote_url_and_maybe_fetch(object, options \\ []) do + quote_url = + case Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes.fix_quote_url(object) do + %{"quoteUrl" => quote_url} -> quote_url + _ -> nil + end - def fix_quote_url(%{"quoteUrl" => quote_url} = object, options) - when not is_nil(quote_url) do - with {:ok, quoted_object} <- get_obj_helper(quote_url, options), + with {:quoting?, true} <- {:quoting?, not is_nil(quote_url)}, + {:ok, quoted_object} <- get_obj_helper(quote_url, options), %Activity{} <- Activity.get_create_by_object_ap_id(quoted_object.data["id"]) do Map.put(object, "quoteUrl", quoted_object.data["id"]) else + {:quoting?, _} -> + object + e -> Logger.warn("Couldn't fetch #{inspect(quote_url)}, error: #{inspect(e)}") object end end - # Fedibird - # https://github.com/fedibird/mastodon/commit/dbd7ae6cf58a92ec67c512296b4daaea0d01e6ac - def fix_quote_url(%{"quoteUri" => quote_url} = object, options) do - object - |> Map.put("quoteUrl", quote_url) - |> fix_quote_url(options) - end - - # Old Fedibird (bug) - # https://github.com/fedibird/mastodon/issues/9 - def fix_quote_url(%{"quoteURL" => quote_url} = object, options) do - object - |> Map.put("quoteUrl", quote_url) - |> fix_quote_url(options) - end - - # Misskey fallback - def fix_quote_url(%{"_misskey_quote" => quote_url} = object, options) do - object - |> Map.put("quoteUrl", quote_url) - |> fix_quote_url(options) - end - - def fix_quote_url(object, _options), do: object - defp prepare_in_reply_to(in_reply_to) do cond do is_bitstring(in_reply_to) -> @@ -493,7 +475,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> strip_internal_fields() |> fix_type(fetch_options) |> fix_in_reply_to(fetch_options) - |> fix_quote_url(fetch_options) + |> fix_quote_url_and_maybe_fetch(fetch_options) data = Map.put(data, "object", object) options = Keyword.put(options, :local, false) diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index 56324a9fa..4e7950547 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -60,7 +60,12 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes(:u, ["lang"]) Meta.allow_tag_with_these_attributes(:ul, ["lang"]) - Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "recipients-inline", "quote-inline"]) + Meta.allow_tag_with_this_attribute_values(:span, "class", [ + "h-card", + "recipients-inline", + "quote-inline" + ]) + Meta.allow_tag_with_these_attributes(:span, ["lang"]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"]) From 9bcec87aba5ce4de6b61b5a95d6832da9dfa0fd8 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 10 Jul 2023 18:27:23 -0400 Subject: [PATCH 0363/1008] Allow local quote and private self-quote --- lib/pleroma/web/common_api/activity_draft.ex | 14 +++++++++++++- .../pleroma/web/common_api/activity_draft_test.exs | 9 ++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index d4875765c..32921aa5a 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -167,9 +167,21 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end + defp can_quote?(_draft, _object, visibility) when visibility in ~w(public unlisted local) do + true + end + + defp can_quote?(draft, object, "private") do + draft.user.ap_id == object.data["actor"] + end + + defp can_quote?(_, _, _) do + false + end + defp quoting_visibility(%{quote_post: %Activity{}} = draft) do with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false), - visibility when visibility in ~w(public unlisted) <- Visibility.get_visibility(object) do + true <- can_quote?(draft, object, Visibility.get_visibility(object)) do draft else _ -> add_error(draft, dgettext("errors", "Cannot quote private message")) diff --git a/test/pleroma/web/common_api/activity_draft_test.exs b/test/pleroma/web/common_api/activity_draft_test.exs index 8a09fc710..02bc6cf3b 100644 --- a/test/pleroma/web/common_api/activity_draft_test.exs +++ b/test/pleroma/web/common_api/activity_draft_test.exs @@ -12,15 +12,22 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraftTest do test "create/2 with a quote post" do user = insert(:user) + another_user = insert(:user) {:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) {:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"}) {:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) + {:ok, local} = CommonAPI.post(user, %{status: ".", visibility: "local"}) {:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"}) {:error, _} = ActivityDraft.create(user, %{status: "nice", quote_id: direct.id}) - {:error, _} = ActivityDraft.create(user, %{status: "nice", quote_id: private.id}) + {:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: private.id}) + {:error, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: private.id}) {:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: unlisted.id}) + {:ok, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: unlisted.id}) + {:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: local.id}) + {:ok, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: local.id}) {:ok, _} = ActivityDraft.create(user, %{status: "nice", quote_id: public.id}) + {:ok, _} = ActivityDraft.create(another_user, %{status: "nice", quote_id: public.id}) end end From d244c9d2984d21887f50737597fc03d2d0dd1601 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 10 Jul 2023 18:28:13 -0400 Subject: [PATCH 0364/1008] Add changelog --- changelog.d/quote.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/quote.add diff --git a/changelog.d/quote.add b/changelog.d/quote.add new file mode 100644 index 000000000..1c368ae75 --- /dev/null +++ b/changelog.d/quote.add @@ -0,0 +1 @@ +Implement quotes From 762794eed9e6fc8a03d1416e2a6e080b00df9e10 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 10 Jul 2023 19:43:18 -0400 Subject: [PATCH 0365/1008] Fix CommonAPITest --- test/pleroma/web/common_api_test.exs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index c4eba8b9c..70a5b6fed 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -825,16 +825,23 @@ defmodule Pleroma.Web.CommonAPITest do test "quote posting visibility" do user = insert(:user) + another_user = insert(:user) {:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) {:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"}) {:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) + {:ok, local} = CommonAPI.post(user, %{status: ".", visibility: "local"}) {:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"}) {:error, _} = CommonAPI.post(user, %{status: "nice", quote_id: direct.id}) - {:error, _} = CommonAPI.post(user, %{status: "nice", quote_id: private.id}) + {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: private.id}) + {:error, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: private.id}) {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: unlisted.id}) + {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: unlisted.id}) + {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: local.id}) + {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: local.id}) {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: public.id}) + {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: public.id}) end end From 163e5637335f9454688d3cc83530f82fc640a5b9 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 09:30:43 -0400 Subject: [PATCH 0366/1008] Allow more flexibility in InlineQuotePolicy --- config/config.exs | 2 +- config/description.exs | 18 ++++++++++++++ docs/configuration/cheatsheet.md | 2 +- .../activity_pub/mrf/inline_quote_policy.ex | 12 ++++++---- priv/scrubbers/default.ex | 1 + .../mrf/inline_quote_policy_test.exs | 24 +++++++++++++++++-- 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/config/config.exs b/config/config.exs index 9149e925a..e8ae31542 100644 --- a/config/config.exs +++ b/config/config.exs @@ -434,7 +434,7 @@ config :pleroma, :mrf_object_age, config :pleroma, :mrf_follow_bot, follower_nickname: nil -config :pleroma, :mrf_inline_quote, prefix: "RT" +config :pleroma, :mrf_inline_quote, template: "RT: {url}" config :pleroma, :rich_media, enabled: true, diff --git a/config/description.exs b/config/description.exs index d18649ae8..079d187d5 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2994,6 +2994,24 @@ config :pleroma, :config_description, [ } ] }, + %{ + group: :pleroma, + key: :mrf_inline_quote, + tab: :mrf, + related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy", + label: "MRF Inline Quote Policy", + type: :group, + description: "Force quote url to appear in post content.", + children: [ + %{ + key: :template, + type: :string, + description: + "The template to append to the post. `{url}` will be replaced with the actual link to the quoted post.", + suggestions: ["RT: {url}"] + } + ] + }, %{ group: :pleroma, key: :modules, diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 32cc5811a..a17f8735a 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -269,7 +269,7 @@ Notes: * `federated_timeline_removal_shortcode`: A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html). #### :mrf_inline_quote -* `prefix`: Prefix before the link (default: `RT`) +* `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `RT: {url}` ### :activitypub * `unfollow_blocked`: Whether blocks result in people getting unfollowed diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex index c78675caf..a0eefefc0 100644 --- a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -6,8 +6,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do @moduledoc "Force a quote line into the message content." @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp build_inline_quote(prefix, url) do - "

#{prefix}: #{url}
" + defp build_inline_quote(template, url) do + quote_line = String.replace(template, "{url}", "#{url}") + + "

#{quote_line}
" end defp has_inline_quote?(content, quote_url) do @@ -27,14 +29,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do if has_inline_quote?(content, quote_url) do object else - prefix = Pleroma.Config.get([:mrf_inline_quote, :prefix]) + template = Pleroma.Config.get([:mrf_inline_quote, :template]) content = if String.ends_with?(content, "

"), do: String.trim_trailing(content, "

") <> - build_inline_quote(prefix, quote_url) <> "

", - else: content <> build_inline_quote(prefix, quote_url) + build_inline_quote(template, quote_url) <> "

", + else: content <> build_inline_quote(template, quote_url) Map.put(object, "content", content) end diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index 4e7950547..24a76263b 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -38,6 +38,7 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes(:abbr, ["title", "lang"]) Meta.allow_tag_with_these_attributes(:b, ["lang"]) + Meta.allow_tag_with_these_attributes(:bdi, []) Meta.allow_tag_with_these_attributes(:blockquote, ["lang"]) Meta.allow_tag_with_these_attributes(:br, ["lang"]) Meta.allow_tag_with_these_attributes(:code, ["lang"]) diff --git a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs index 44ee91d4b..d5762766f 100644 --- a/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/inline_quote_policy_test.exs @@ -22,7 +22,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) assert filtered == - "Nice post

RT: https://gleasonator.com/objects/1234
" + "Nice post

RT: https://gleasonator.com/objects/1234
" + end + + test "adds quote URL to post content, custom template" do + clear_config([:mrf_inline_quote, :template], "{url}'s quoting") + quote_url = "https://gleasonator.com/objects/1234" + + activity = %{ + "type" => "Create", + "actor" => "https://gleasonator.com/users/alex", + "object" => %{ + "type" => "Note", + "content" => "Nice post", + "quoteUrl" => quote_url + } + } + + {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) + + assert filtered == + "Nice post

https://gleasonator.com/objects/1234's quoting
" end test "doesn't add line breaks to markdown posts" do @@ -41,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicyTest do {:ok, %{"object" => %{"content" => filtered}}} = InlineQuotePolicy.filter(activity) assert filtered == - "

Nice post

RT: https://gleasonator.com/objects/1234

" + "

Nice post

RT: https://gleasonator.com/objects/1234

" end test "ignores Misskey quote posts" do From e9cd004ba1b904d92b1c07446bbf03dc070cce6a Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 11:09:10 -0400 Subject: [PATCH 0367/1008] Parse object link as quoteUrl --- lib/pleroma/constants.ex | 7 +++++ .../audio_image_video_validator.ex | 1 + .../object_validators/common_fixes.ex | 27 +++++++++++++++++++ .../object_validators/question_validator.ex | 1 + .../quote_post/fep-e232-tag-example.json | 17 ++++++++++++ .../article_note_page_validator_test.exs | 12 +++++++++ 6 files changed, 65 insertions(+) create mode 100644 test/fixtures/quote_post/fep-e232-tag-example.json diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 6befc6897..9d764ec25 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -83,4 +83,11 @@ defmodule Pleroma.Constants do ) const(upload_object_types, do: ["Document", "Image"]) + + const(activity_json_mime_types, + do: [ + "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", + "application/activity+json" + ] + ) end diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex index 79ff76104..65ac6bb93 100644 --- a/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/audio_image_video_validator.ex @@ -99,6 +99,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do data |> CommonFixes.fix_actor() |> CommonFixes.fix_object_defaults() + |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() |> fix_url() |> fix_content() diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index cc2ad9116..65b8d9a2c 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -10,6 +10,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils + require Pleroma.Constants + def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do {:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback) @@ -96,5 +98,30 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do Map.put(data, "quoteUrl", quote_url) end + def fix_quote_url(%{"tag" => [_ | _] = tags} = data) do + tag = Enum.find(tags, &is_object_link_tag/1) + + if not is_nil(tag) do + data + |> Map.put("quoteUrl", tag["href"]) + else + data + end + end + def fix_quote_url(data), do: data + + # https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md + defp is_object_link_tag( + %{ + "type" => "Link", + "mediaType" => media_type, + "href" => href + } = tag + ) + when media_type in Pleroma.Constants.activity_json_mime_types() and is_binary(href) do + true + end + + defp is_object_link_tag(_), do: false end diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index ce3305142..621085e6c 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -62,6 +62,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do data |> CommonFixes.fix_actor() |> CommonFixes.fix_object_defaults() + |> CommonFixes.fix_quote_url() |> Transmogrifier.fix_emoji() |> fix_closed() end diff --git a/test/fixtures/quote_post/fep-e232-tag-example.json b/test/fixtures/quote_post/fep-e232-tag-example.json new file mode 100644 index 000000000..23c7fb5ac --- /dev/null +++ b/test/fixtures/quote_post/fep-e232-tag-example.json @@ -0,0 +1,17 @@ +{ + "@context": "https://www.w3.org/ns/activitystreams", + "type": "Note", + "content": "This is a quote:
RE: https://server.example/objects/123", + "tag": [ + { + "type": "Link", + "mediaType": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", + "href": "https://server.example/objects/123", + "name": "RE: https://server.example/objects/123" + } + ], + "id": "https://server.example/objects/1", + "to": "https://server.example/users/1", + "attributedTo": "https://server.example/users/1", + "actor": "https://server.example/users/1" +} diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index a4ba38e6a..73141cac1 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -146,4 +146,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest assert cng.valid? assert cng.changes.quoteUrl == "https://misskey.io/notes/8vs6wxufd0" end + + test "Parse tag as quote" do + # https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md + + insert(:user, ap_id: "https://server.example/users/1") + + data = File.read!("test/fixtures/quote_post/fep-e232-tag-example.json") |> Jason.decode!() + cng = ArticleNotePageValidator.cast_and_validate(data) + + assert cng.valid? + assert cng.changes.quoteUrl == "https://server.example/objects/123" + end end From 479a6f11dbbba0c945c08883956ffab198f91688 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 14:08:24 -0400 Subject: [PATCH 0368/1008] Keep incoming Link tag --- .../object_validators/tag_validator.ex | 14 +++++++++++++- .../article_note_page_validator_test.exs | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex index cfd510c19..47cf7b415 100644 --- a/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/tag_validator.ex @@ -9,15 +9,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do import Ecto.Changeset + require Pleroma.Constants + @primary_key false embedded_schema do # Common field(:type, :string) field(:name, :string) - # Mention, Hashtag + # Mention, Hashtag, Link field(:href, ObjectValidators.Uri) + # Link + field(:mediaType, :string) + # Emoji embeds_one :icon, IconObjectValidator, primary_key: false do field(:type, :string) @@ -68,6 +73,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do |> validate_required([:type, :name, :icon]) end + def changeset(struct, %{"type" => "Link"} = data) do + struct + |> cast(data, [:type, :name, :mediaType, :href]) + |> validate_inclusion(:mediaType, Pleroma.Constants.activity_json_mime_types()) + |> validate_required([:type, :href, :mediaType]) + end + def changeset(struct, %{"type" => _} = data) do struct |> cast(data, []) diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs index 73141cac1..4703c3801 100644 --- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -157,5 +157,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest assert cng.valid? assert cng.changes.quoteUrl == "https://server.example/objects/123" + + assert Enum.at(cng.changes.tag, 0).changes == %{ + type: "Link", + mediaType: "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", + href: "https://server.example/objects/123", + name: "RE: https://server.example/objects/123" + } end end From e349e92a441840bbbdbf13cacd307e65f85a38ff Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 14:27:29 -0400 Subject: [PATCH 0369/1008] Add mrf to force link tag of quoting posts --- docs/configuration/cheatsheet.md | 1 + lib/pleroma/constants.ex | 4 + .../mrf/quote_to_link_tag_policy.ex | 49 +++++++++++++ .../object_validators/common_fixes.ex | 16 ++-- .../mrf/quote_to_link_tag_policy_test.exs | 73 +++++++++++++++++++ 5 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex create mode 100644 test/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy_test.exs diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index a17f8735a..a4cae4dbb 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -161,6 +161,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline. + * `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions) * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 9d764ec25..ed60bcc37 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -84,6 +84,10 @@ defmodule Pleroma.Constants do const(upload_object_types, do: ["Document", "Image"]) + const(activity_json_canonical_mime_type, + do: "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + ) + const(activity_json_mime_types, do: [ "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", diff --git a/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex new file mode 100644 index 000000000..f1c573d1b --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex @@ -0,0 +1,49 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do + @moduledoc "Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions)" + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes + + require Pleroma.Constants + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do + {:ok, Map.put(activity, "object", filter_object(object))} + end + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def filter(object), do: {:ok, object} + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def describe, do: {:ok, %{}} + + @impl Pleroma.Web.ActivityPub.MRF.Policy + def history_awareness, do: :auto + + defp filter_object(%{"quoteUrl" => quote_url} = object) do + tags = object["tag"] || [] + + if Enum.any?(tags, fn tag -> + CommonFixes.is_object_link_tag(tag) and tag["href"] == quote_url + end) do + object + else + object + |> Map.put( + "tag", + tags ++ + [ + %{ + "type" => "Link", + "mediaType" => Pleroma.Constants.activity_json_canonical_mime_type(), + "href" => quote_url + } + ] + ) + end + end +end diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 65b8d9a2c..4d9be0bdd 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -112,16 +112,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do def fix_quote_url(data), do: data # https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md - defp is_object_link_tag( - %{ - "type" => "Link", - "mediaType" => media_type, - "href" => href - } = tag - ) - when media_type in Pleroma.Constants.activity_json_mime_types() and is_binary(href) do + def is_object_link_tag(%{ + "type" => "Link", + "mediaType" => media_type, + "href" => href + }) + when media_type in Pleroma.Constants.activity_json_mime_types() and is_binary(href) do true end - defp is_object_link_tag(_), do: false + def is_object_link_tag(_), do: false end diff --git a/test/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy_test.exs new file mode 100644 index 000000000..96b49b6a0 --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy_test.exs @@ -0,0 +1,73 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicyTest do + alias Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy + + use Pleroma.DataCase + + require Pleroma.Constants + + test "Add quote url to Link tag" do + quote_url = "https://gleasonator.com/objects/1234" + + activity = %{ + "type" => "Create", + "actor" => "https://gleasonator.com/users/alex", + "object" => %{ + "type" => "Note", + "content" => "Nice post", + "quoteUrl" => quote_url + } + } + + {:ok, %{"object" => object}} = QuoteToLinkTagPolicy.filter(activity) + + assert object["tag"] == [ + %{ + "type" => "Link", + "href" => quote_url, + "mediaType" => Pleroma.Constants.activity_json_canonical_mime_type() + } + ] + end + + test "Add quote url to Link tag, append to the end" do + quote_url = "https://gleasonator.com/objects/1234" + + activity = %{ + "type" => "Create", + "actor" => "https://gleasonator.com/users/alex", + "object" => %{ + "type" => "Note", + "content" => "Nice post", + "quoteUrl" => quote_url, + "tag" => [%{"type" => "Hashtag", "name" => "#foo"}] + } + } + + {:ok, %{"object" => object}} = QuoteToLinkTagPolicy.filter(activity) + + assert [_, tag] = object["tag"] + + assert tag == %{ + "type" => "Link", + "href" => quote_url, + "mediaType" => Pleroma.Constants.activity_json_canonical_mime_type() + } + end + + test "Bypass posts without quoteUrl" do + activity = %{ + "type" => "Create", + "actor" => "https://gleasonator.com/users/alex", + "object" => %{ + "type" => "Note", + "content" => "Nice post" + } + } + + assert {:ok, ^activity} = QuoteToLinkTagPolicy.filter(activity) + end +end From 8b98a98dfb4ab3db9b4f2347713d86ed9c87b61b Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 14:37:12 -0400 Subject: [PATCH 0370/1008] Make InlineQuotePolicy history aware --- lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex index a0eefefc0..aaa209aa1 100644 --- a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -53,6 +53,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do @impl true def describe, do: {:ok, %{}} + @impl Pleroma.Web.ActivityPub.MRF.Policy + def history_awareness, do: :auto + @impl true def config_description do %{ From 8596f926543126efdb4b8cfe70beab6812824398 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 14:58:20 -0400 Subject: [PATCH 0371/1008] Fix TransmogrifierTest --- test/pleroma/web/activity_pub/transmogrifier_test.exs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index 4a192cdc0..5e58d75db 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -123,7 +123,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert activity.data["context"] == object.data["context"] end - test "it drops link tags" do + test "it keeps link tags" do insert(:user, ap_id: "https://example.org/users/alice") message = File.read!("test/fixtures/fep-e232.json") |> Jason.decode!() @@ -131,10 +131,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert {:ok, activity} = Transmogrifier.handle_incoming(message) object = Object.normalize(activity) - assert length(object.data["tag"]) == 1 - - tag = object.data["tag"] |> List.first() - assert tag["type"] == "Mention" + assert [%{"type" => "Mention"}, %{"type" => "Link"}] = object.data["tag"] end test "it accepts quote posts" do From 87353e5ad12799d12507253fe9a0363fd9f0c817 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 22:07:16 -0400 Subject: [PATCH 0372/1008] Fix config descriptions for mrf inline quote --- config/description.exs | 18 ------------------ .../activity_pub/mrf/inline_quote_policy.ex | 12 +++++++----- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/config/description.exs b/config/description.exs index 079d187d5..d18649ae8 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2994,24 +2994,6 @@ config :pleroma, :config_description, [ } ] }, - %{ - group: :pleroma, - key: :mrf_inline_quote, - tab: :mrf, - related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy", - label: "MRF Inline Quote Policy", - type: :group, - description: "Force quote url to appear in post content.", - children: [ - %{ - key: :template, - type: :string, - description: - "The template to append to the post. `{url}` will be replaced with the actual link to the quoted post.", - suggestions: ["RT: {url}"] - } - ] - }, %{ group: :pleroma, key: :modules, diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex index aaa209aa1..171b22c5e 100644 --- a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -61,14 +61,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do %{ key: :mrf_inline_quote, related_policy: "Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy", - label: "MRF Inline Quote", - description: "Force quote post URLs inline", + label: "MRF Inline Quote Policy", + type: :group, + description: "Force quote url to appear in post content.", children: [ %{ - key: :prefix, + key: :template, type: :string, - description: "Prefix before the link", - suggestions: ["RT", "QT", "RE", "RN"] + description: + "The template to append to the post. `{url}` will be replaced with the actual link to the quoted post.", + suggestions: ["RT: {url}"] } ] } From 875b46d97d910ffd2c33ac26ed8dfe38f7672f52 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 23:29:23 -0400 Subject: [PATCH 0373/1008] Do not mention original poster when quoting --- lib/pleroma/web/common_api/activity_draft.ex | 3 --- test/pleroma/web/common_api_test.exs | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index 32921aa5a..ca1329284 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -139,9 +139,6 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do case Activity.get_by_id_with_object(id) do - %Activity{actor: actor_ap_id} = activity when not_empty_string(actor_ap_id) -> - %__MODULE__{draft | quote_post: activity, mentions: [actor_ap_id]} - %Activity{} = activity -> %__MODULE__{draft | quote_post: activity} diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 70a5b6fed..a98b16d4b 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -808,8 +808,8 @@ defmodule Pleroma.Web.CommonAPITest do assert quote_post.data["quoteUrl"] == quoted.data["id"] - # The OP is mentioned - assert quoted.data["actor"] in quote_post.data["to"] + # The OP is not mentioned + refute quoted.data["actor"] in quote_post.data["to"] end test "quote posting with explicit addressing doesn't mention the OP" do From a8b2f9205d16465a3b11d3802c966db3da908c5d Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 23:47:31 -0400 Subject: [PATCH 0374/1008] Expose quote_id parameter on the api --- lib/pleroma/web/api_spec/schemas/status.ex | 5 +++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 10 ++++++++++ .../web/mastodon_api/views/status_view_test.exs | 3 +++ 3 files changed, 18 insertions(+) diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 5d0eedb08..07f03134a 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -198,6 +198,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do nullable: true, description: "Quoted status (if any)" }, + quote_id: %Schema{ + nullable: true, + allOf: [FlakeID], + description: "ID of the status being quoted, if any" + }, quote_url: %Schema{ type: :string, format: :uri, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index ba4a8f3eb..3d3039751 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -312,6 +312,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # Here the implicit index of the current content is 0 chrono_order = history_len - 1 + quote_id = get_quote_id(activity) + quote_activity = get_quote(activity, opts) quote_post = @@ -431,6 +433,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do context: object.data["context"], in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, quote: quote_post, + quote_id: quote_id, quote_url: object.data["quoteUrl"], quote_visible: visible_for_user?(quote_activity, opts[:for]), content: %{"text/plain" => content_plaintext}, @@ -689,6 +692,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end + defp get_quote_id(activity) do + case get_quote(activity, %{}) do + %Activity{id: id} -> id + _ -> nil + end + end + def render_content(%{data: %{"name" => name}} = object) when not is_nil(name) and name != "" do url = object.data["url"] || object.data["id"] diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index 6d3a72970..221244d4e 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -327,6 +327,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do context: object_data["context"], in_reply_to_account_acct: nil, quote: nil, + quote_id: nil, quote_url: nil, quote_visible: false, content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, @@ -435,10 +436,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status = StatusView.render("show.json", %{activity: quoted_quote_post}) assert status.pleroma.quote.id == to_string(quote_post.id) + assert status.pleroma.quote_id == to_string(quote_post.id) assert status.pleroma.quote_url == Object.normalize(quote_post).data["id"] # Quotes don't go more than one level deep refute status.pleroma.quote.pleroma.quote + assert status.pleroma.quote.pleroma.quote_id == to_string(post.id) assert status.pleroma.quote.pleroma.quote_url == Object.normalize(post).data["id"] # In an index From 08608afca5566f712acdc14b7c43976d6d071106 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 23:56:54 -0400 Subject: [PATCH 0375/1008] Fix quote_visible attribute --- .../web/mastodon_api/views/status_view.ex | 19 +++++++------------ .../mastodon_api/views/status_view_test.exs | 2 ++ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3d3039751..d070262cc 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -312,12 +312,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # Here the implicit index of the current content is 0 chrono_order = history_len - 1 - quote_id = get_quote_id(activity) - quote_activity = get_quote(activity, opts) + quote_id = + case quote_activity do + %Activity{id: id} -> id + _ -> nil + end + quote_post = - if visible_for_user?(quote_activity, opts[:for]) do + if visible_for_user?(quote_activity, opts[:for]) and opts[:show_quote] != false do quote_rendering_opts = Map.merge(opts, %{activity: quote_activity, show_quote: false}) render("show.json", quote_rendering_opts) else @@ -671,8 +675,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - def get_quote(_activity, %{show_quote: false}), do: nil - def get_quote(activity, %{quoted_activities: quoted_activities}) do object = Object.normalize(activity, fetch: false) @@ -692,13 +694,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end - defp get_quote_id(activity) do - case get_quote(activity, %{}) do - %Activity{id: id} -> id - _ -> nil - end - end - def render_content(%{data: %{"name" => name}} = object) when not is_nil(name) and name != "" do url = object.data["url"] || object.data["id"] diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index 221244d4e..baa9b32f5 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -438,11 +438,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do assert status.pleroma.quote.id == to_string(quote_post.id) assert status.pleroma.quote_id == to_string(quote_post.id) assert status.pleroma.quote_url == Object.normalize(quote_post).data["id"] + assert status.pleroma.quote_visible # Quotes don't go more than one level deep refute status.pleroma.quote.pleroma.quote assert status.pleroma.quote.pleroma.quote_id == to_string(post.id) assert status.pleroma.quote.pleroma.quote_url == Object.normalize(post).data["id"] + assert status.pleroma.quote.pleroma.quote_visible # In an index [status] = StatusView.render("index.json", %{activities: [quoted_quote_post], as: :activity}) From 2f6fc6a7ab0e757abfe9ec535842a12b887d2fe6 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Sun, 24 Sep 2023 22:52:41 +0200 Subject: [PATCH 0376/1008] TwitterAPI: Return proper error when healthcheck is disabled --- changelog.d/healthcheck-disabled-error.fix | 1 + lib/pleroma/web/twitter_api/controllers/util_controller.ex | 5 ++++- test/pleroma/web/twitter_api/util_controller_test.exs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog.d/healthcheck-disabled-error.fix diff --git a/changelog.d/healthcheck-disabled-error.fix b/changelog.d/healthcheck-disabled-error.fix new file mode 100644 index 000000000..984384a52 --- /dev/null +++ b/changelog.d/healthcheck-disabled-error.fix @@ -0,0 +1 @@ +TwitterAPI: Return proper error when healthcheck is disabled diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index d5a24ae6c..ca8a98960 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -345,13 +345,16 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end def healthcheck(conn, _params) do - with true <- Config.get([:instance, :healthcheck]), + with {:cfg, true} <- {:cfg, Config.get([:instance, :healthcheck])}, %{healthy: true} = info <- Healthcheck.system_info() do json(conn, info) else %{healthy: false} = info -> service_unavailable(conn, info) + {:cfg, false} -> + service_unavailable(conn, %{"error" => "Healthcheck disabled"}) + _ -> service_unavailable(conn, %{}) end diff --git a/test/pleroma/web/twitter_api/util_controller_test.exs b/test/pleroma/web/twitter_api/util_controller_test.exs index a4da23635..d06ae71aa 100644 --- a/test/pleroma/web/twitter_api/util_controller_test.exs +++ b/test/pleroma/web/twitter_api/util_controller_test.exs @@ -106,7 +106,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do |> get("/api/pleroma/healthcheck") |> json_response_and_validate_schema(503) - assert response == %{} + assert response == %{"error" => "Healthcheck disabled"} end test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do From 2b5636bf127b1987736c281d346a5771c8168c09 Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 31 Mar 2023 21:47:37 -0400 Subject: [PATCH 0377/1008] Allow unified streaming endpoint --- .../web/mastodon_api/websocket_handler.ex | 17 ++++++++++++----- lib/pleroma/web/streamer.ex | 8 ++++++-- .../integration/mastodon_websocket_test.exs | 5 ++++- test/pleroma/web/streamer_test.exs | 4 ++++ 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 88444106d..97a1f1401 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -32,8 +32,15 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do req end + topics = + if topic do + [topic] + else + [] + end + {:cowboy_websocket, req, - %{user: user, topic: topic, oauth_token: oauth_token, count: 0, timer: nil}, + %{user: user, topics: topics, oauth_token: oauth_token, count: 0, timer: nil}, %{idle_timeout: @timeout}} else {:error, :bad_topic} -> @@ -50,10 +57,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do def websocket_init(state) do Logger.debug( - "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}" + "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics}" ) - Streamer.add_socket(state.topic, state.oauth_token) + Enum.each(state.topics, fn topic -> Streamer.add_socket(topic, state.oauth_token) end) {:ok, %{state | timer: timer()}} end @@ -109,10 +116,10 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do def terminate(reason, _req, state) do Logger.debug( - "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}" + "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)}" ) - Streamer.remove_socket(state.topic) + Enum.each(state.topics, fn topic -> Streamer.remove_socket(topic) end) :ok end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index b9a04cc76..6b5fdbf0c 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -59,10 +59,14 @@ defmodule Pleroma.Web.Streamer do end @doc "Expand and authorizes a stream" - @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) :: - {:ok, topic :: String.t()} | {:error, :bad_topic} + @spec get_topic(stream :: String.t() | nil, User.t() | nil, Token.t() | nil, Map.t()) :: + {:ok, topic :: String.t() | nil} | {:error, :bad_topic} def get_topic(stream, user, oauth_token, params \\ %{}) + def get_topic(nil = _stream, _user, _oauth_token, _params) do + {:ok, nil} + end + # Allow all public steams if the instance allows unauthenticated access. # Otherwise, only allow users with valid oauth tokens. def get_topic(stream, user, oauth_token, _params) when stream in @public_streams do diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 9be0445c0..3b120c10e 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -33,7 +33,6 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do test "refuses invalid requests" do capture_log(fn -> - assert {:error, %WebSockex.RequestError{code: 404}} = start_socket() assert {:error, %WebSockex.RequestError{code: 404}} = start_socket("?stream=ncjdk") Process.sleep(30) end) @@ -49,6 +48,10 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do end) end + test "allows unified stream" do + assert {:ok, _} = start_socket() + end + test "allows public streams without authentication" do assert {:ok, _} = start_socket("?stream=public") assert {:ok, _} = start_socket("?stream=public:local") diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index 7ab0e379b..da97b87d8 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -22,6 +22,10 @@ defmodule Pleroma.Web.StreamerTest do setup do: clear_config([:instance, :skip_thread_containment]) describe "get_topic/_ (unauthenticated)" do + test "allows no stream" do + assert {:ok, nil} = Streamer.get_topic(nil, nil, nil) + end + test "allows public" do assert {:ok, "public"} = Streamer.get_topic("public", nil, nil) assert {:ok, "public:local"} = Streamer.get_topic("public:local", nil, nil) From 273cda63ad79b61f4d37e4b7603694908e894e4f Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 31 Mar 2023 22:55:52 -0400 Subject: [PATCH 0378/1008] Allow subscribing to streams --- .../web/mastodon_api/websocket_handler.ex | 74 +++++++++++++++ lib/pleroma/web/views/streamer_view.ex | 18 ++++ .../integration/mastodon_websocket_test.exs | 90 +++++++++++++++++++ 3 files changed, 182 insertions(+) diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 97a1f1401..a42a9a63c 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.User alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Streamer + alias Pleroma.Web.StreamerView @behaviour :cowboy_websocket @@ -73,6 +74,16 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do # We only receive pings for now def websocket_handle(:ping, state), do: {:ok, state} + def websocket_handle({:text, text}, state) do + with {:ok, %{} = event} <- Jason.decode(text) do + handle_client_event(event, state) + else + _ -> + Logger.error("#{__MODULE__} received non-JSON event: #{inspect(text)}") + {:ok, state} + end + end + def websocket_handle(frame, state) do Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") {:ok, state} @@ -144,4 +155,67 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do defp timer do Process.send_after(self(), :tick, @tick) end + + defp handle_client_event(%{"type" => "subscribe", "stream" => _topic} = params, state) do + with {_, {:ok, topic}} <- + {:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)}, + {_, false} <- {:subscribed, topic in state.topics} do + Streamer.add_socket(topic, state.oauth_token) + + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"})} + ], %{state | topics: [topic | state.topics]}} + else + {:subscribed, true} -> + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"})} + ], state} + + {:topic, {:error, error}} -> + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{ + type: "subscribe", + result: "error", + error: error + })} + ], state} + end + end + + defp handle_client_event(%{"type" => "unsubscribe", "stream" => _topic} = params, state) do + with {_, {:ok, topic}} <- + {:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)}, + {_, true} <- {:subscribed, topic in state.topics} do + Streamer.remove_socket(topic) + + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"})} + ], %{state | topics: List.delete(state.topics, topic)}} + else + {:subscribed, false} -> + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"})} + ], state} + + {:topic, {:error, error}} -> + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{ + type: "unsubscribe", + result: "error", + error: error + })} + ], state} + end + end + + defp handle_client_event(params, state) do + Logger.error("#{__MODULE__} received unknown event: #{inspect(params)}") + {[], state} + end end diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 6a55242b0..19f098783 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -135,4 +135,22 @@ defmodule Pleroma.Web.StreamerView do } |> Jason.encode!() end + + def render("pleroma_respond.json", %{type: type, result: result} = params) do + %{ + event: "pleroma.respond", + payload: + %{ + result: result, + type: type + } + |> Map.merge(maybe_error(params)) + |> Jason.encode!() + } + |> Jason.encode!() + end + + defp maybe_error(%{error: :bad_topic}), do: %{error: "bad_topic"} + defp maybe_error(%{error: :unauthorized}), do: %{error: "unauthorized"} + defp maybe_error(_), do: %{} end diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 3b120c10e..9db0f714f 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -31,6 +31,13 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do WebsocketClient.start_link(self(), path, headers) end + defp decode_json(json) do + with {:ok, %{"event" => event, "payload" => payload_text}} <- Jason.decode(json), + {:ok, payload} <- Jason.decode(payload_text) do + {:ok, %{"event" => event, "payload" => payload}} + end + end + test "refuses invalid requests" do capture_log(fn -> assert {:error, %WebSockex.RequestError{code: 404}} = start_socket("?stream=ncjdk") @@ -79,6 +86,89 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert json == view_json end + describe "subscribing via WebSocket" do + test "can subscribe" do + user = insert(:user) + {:ok, pid} = start_socket() + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "subscribe", "result" => "success"} + }} = decode_json(raw_json) + + {:ok, activity} = CommonAPI.post(user, %{status: "nice echo chamber"}) + + assert_receive {:text, raw_json}, 1_000 + assert {:ok, json} = Jason.decode(raw_json) + + assert "update" == json["event"] + assert json["payload"] + assert {:ok, json} = Jason.decode(json["payload"]) + + view_json = + Pleroma.Web.MastodonAPI.StatusView.render("show.json", activity: activity, for: nil) + |> Jason.encode!() + |> Jason.decode!() + + assert json == view_json + end + + test "won't double subscribe" do + user = insert(:user) + {:ok, pid} = start_socket() + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "subscribe", "result" => "success"} + }} = decode_json(raw_json) + + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "subscribe", "result" => "ignored"} + }} = decode_json(raw_json) + + {:ok, _activity} = CommonAPI.post(user, %{status: "nice echo chamber"}) + + assert_receive {:text, _}, 1_000 + refute_receive {:text, _}, 1_000 + end + + test "can unsubscribe" do + user = insert(:user) + {:ok, pid} = start_socket() + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "subscribe", "result" => "success"} + }} = decode_json(raw_json) + + WebsocketClient.send_text(pid, %{type: "unsubscribe", stream: "public"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "unsubscribe", "result" => "success"} + }} = decode_json(raw_json) + + {:ok, _activity} = CommonAPI.post(user, %{status: "nice echo chamber"}) + refute_receive {:text, _}, 1_000 + end + end + describe "with a valid user token" do setup do {:ok, app} = From 21395aa5090f2a53bdbe0ef5fac46693d16025ed Mon Sep 17 00:00:00 2001 From: tusooa Date: Fri, 31 Mar 2023 23:19:57 -0400 Subject: [PATCH 0379/1008] Allow authenticating via client-sent events --- .../web/mastodon_api/websocket_handler.ex | 36 +++++++++ lib/pleroma/web/views/streamer_view.ex | 1 + .../integration/mastodon_websocket_test.exs | 81 +++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index a42a9a63c..6233c3340 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -214,6 +214,42 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end + defp handle_client_event( + %{"type" => "pleroma.authenticate", "token" => access_token} = _params, + state + ) do + with {:auth, nil, nil} <- {:auth, state.user, state.oauth_token}, + {:ok, user, oauth_token} <- authenticate_request(access_token, nil) do + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma.authenticate", + result: "success" + })} + ], %{state | user: user, oauth_token: oauth_token}} + else + {:auth, _, _} -> + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma.authenticate", + result: "error", + error: :already_authenticated + })} + ], state} + + _ -> + {[ + {:text, + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma.authenticate", + result: "error", + error: :unauthorized + })} + ], state} + end + end + defp handle_client_event(params, state) do Logger.error("#{__MODULE__} received unknown event: #{inspect(params)}") {[], state} diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 19f098783..0cdcb1918 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -152,5 +152,6 @@ defmodule Pleroma.Web.StreamerView do defp maybe_error(%{error: :bad_topic}), do: %{error: "bad_topic"} defp maybe_error(%{error: :unauthorized}), do: %{error: "unauthorized"} + defp maybe_error(%{error: :already_authenticated}), do: %{error: "already_authenticated"} defp maybe_error(_), do: %{} end diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 9db0f714f..827c7b5b0 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -224,6 +224,87 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do end) end + test "accepts valid token on client-sent event", %{token: token} do + assert {:ok, pid} = start_socket() + + WebsocketClient.send_text( + pid, + %{type: "pleroma.authenticate", token: token.token} |> Jason.encode!() + ) + + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "pleroma.authenticate", "result" => "success"} + }} = decode_json(raw_json) + + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "user"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "subscribe", "result" => "success"} + }} = decode_json(raw_json) + end + + test "rejects invalid token on client-sent event" do + assert {:ok, pid} = start_socket() + + WebsocketClient.send_text( + pid, + %{type: "pleroma.authenticate", token: "Something else"} |> Jason.encode!() + ) + + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{ + "type" => "pleroma.authenticate", + "result" => "error", + "error" => "unauthorized" + } + }} = decode_json(raw_json) + end + + test "rejects new authenticate request if already logged-in", %{token: token} do + assert {:ok, pid} = start_socket() + + WebsocketClient.send_text( + pid, + %{type: "pleroma.authenticate", token: token.token} |> Jason.encode!() + ) + + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "pleroma.authenticate", "result" => "success"} + }} = decode_json(raw_json) + + WebsocketClient.send_text( + pid, + %{type: "pleroma.authenticate", token: "Something else"} |> Jason.encode!() + ) + + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{ + "type" => "pleroma.authenticate", + "result" => "error", + "error" => "already_authenticated" + } + }} = decode_json(raw_json) + end + test "disconnect when token is revoked", %{app: app, user: user, token: token} do assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}") assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}") From 7d005e8c93b22dc3d7be1a66dd2d404b7f54306a Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 01:25:13 -0400 Subject: [PATCH 0380/1008] Return stream attribute in server-sent events --- lib/pleroma/constants.ex | 4 ++ .../web/mastodon_api/websocket_handler.ex | 4 +- lib/pleroma/web/streamer.ex | 27 +++++---- lib/pleroma/web/views/streamer_view.ex | 42 +++++++++++--- .../integration/mastodon_websocket_test.exs | 33 +++++++++++ test/pleroma/web/streamer_test.exs | 58 +++++++++---------- 6 files changed, 119 insertions(+), 49 deletions(-) diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index ed60bcc37..77bc4bfac 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -94,4 +94,8 @@ defmodule Pleroma.Constants do "application/activity+json" ] ) + + const(public_streams, + do: ["public", "public:local", "public:media", "public:local:media"] + ) end diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 6233c3340..2707673ba 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -89,11 +89,11 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {:ok, state} end - def websocket_info({:render_with_user, view, template, item}, state) do + def websocket_info({:render_with_user, view, template, item, topic}, state) do user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) unless Streamer.filtered_by_user?(user, item) do - websocket_info({:text, view.render(template, item, user)}, %{state | user: user}) + websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user}) else {:ok, state} end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 6b5fdbf0c..70e46617a 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.Streamer do require Logger + require Pleroma.Constants alias Pleroma.Activity alias Pleroma.Chat.MessageReference @@ -24,7 +25,7 @@ defmodule Pleroma.Web.Streamer do def registry, do: @registry - @public_streams ["public", "public:local", "public:media", "public:local:media"] + @public_streams Pleroma.Constants.public_streams() @local_streams ["public:local", "public:local:media"] @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"] @@ -223,8 +224,8 @@ defmodule Pleroma.Web.Streamer do end defp do_stream("follow_relationship", item) do - text = StreamerView.render("follow_relationships_update.json", item) user_topic = "user:#{item.follower.id}" + text = StreamerView.render("follow_relationships_update.json", item, user_topic) Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n") @@ -270,9 +271,11 @@ defmodule Pleroma.Web.Streamer do defp do_stream(topic, %Notification{} = item) when topic in ["user", "user:notification"] do - Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list -> + user_topic = "#{topic}:#{item.user_id}" + + Registry.dispatch(@registry, user_topic, fn list -> Enum.each(list, fn {pid, _auth} -> - send(pid, {:render_with_user, StreamerView, "notification.json", item}) + send(pid, {:render_with_user, StreamerView, "notification.json", item, user_topic}) end) end) end @@ -281,7 +284,7 @@ defmodule Pleroma.Web.Streamer do when topic in ["user", "user:pleroma_chat"] do topic = "#{topic}:#{user.id}" - text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) + text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, topic) Registry.dispatch(@registry, topic, fn list -> Enum.each(list, fn {pid, _auth} -> @@ -309,7 +312,7 @@ defmodule Pleroma.Web.Streamer do end defp push_to_socket(topic, %Participation{} = participation) do - rendered = StreamerView.render("conversation.json", participation) + rendered = StreamerView.render("conversation.json", participation, topic) Registry.dispatch(@registry, topic, fn list -> Enum.each(list, fn {pid, _} -> @@ -337,12 +340,15 @@ defmodule Pleroma.Web.Streamer do Pleroma.Activity.get_create_by_object_ap_id(item.object.data["id"]) |> Map.put(:object, item.object) - anon_render = StreamerView.render("status_update.json", create_activity) + anon_render = StreamerView.render("status_update.json", create_activity, topic) Registry.dispatch(@registry, topic, fn list -> Enum.each(list, fn {pid, auth?} -> if auth? do - send(pid, {:render_with_user, StreamerView, "status_update.json", create_activity}) + send( + pid, + {:render_with_user, StreamerView, "status_update.json", create_activity, topic} + ) else send(pid, {:text, anon_render}) end @@ -351,12 +357,13 @@ defmodule Pleroma.Web.Streamer do end defp push_to_socket(topic, item) do - anon_render = StreamerView.render("update.json", item) + Logger.debug("topic=#{topic}") + anon_render = StreamerView.render("update.json", item, topic) Registry.dispatch(@registry, topic, fn list -> Enum.each(list, fn {pid, auth?} -> if auth? do - send(pid, {:render_with_user, StreamerView, "update.json", item}) + send(pid, {:render_with_user, StreamerView, "update.json", item, topic}) else send(pid, {:text, anon_render}) end diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 0cdcb1918..f591da9a6 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -11,8 +11,11 @@ defmodule Pleroma.Web.StreamerView do alias Pleroma.User alias Pleroma.Web.MastodonAPI.NotificationView - def render("update.json", %Activity{} = activity, %User{} = user) do + require Pleroma.Constants + + def render("update.json", %Activity{} = activity, %User{} = user, topic) do %{ + stream: render("stream.json", %{topic: topic}), event: "update", payload: Pleroma.Web.MastodonAPI.StatusView.render( @@ -25,8 +28,9 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("status_update.json", %Activity{} = activity, %User{} = user) do + def render("status_update.json", %Activity{} = activity, %User{} = user, topic) do %{ + stream: render("stream.json", %{topic: topic}), event: "status.update", payload: Pleroma.Web.MastodonAPI.StatusView.render( @@ -39,8 +43,9 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("notification.json", %Notification{} = notify, %User{} = user) do + def render("notification.json", %Notification{} = notify, %User{} = user, topic) do %{ + stream: render("stream.json", %{topic: topic}), event: "notification", payload: NotificationView.render( @@ -52,8 +57,9 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("update.json", %Activity{} = activity) do + def render("update.json", %Activity{} = activity, topic) do %{ + stream: render("stream.json", %{topic: topic}), event: "update", payload: Pleroma.Web.MastodonAPI.StatusView.render( @@ -65,8 +71,9 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("status_update.json", %Activity{} = activity) do + def render("status_update.json", %Activity{} = activity, topic) do %{ + stream: render("stream.json", %{topic: topic}), event: "status.update", payload: Pleroma.Web.MastodonAPI.StatusView.render( @@ -78,7 +85,7 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("chat_update.json", %{chat_message_reference: cm_ref}) do + def render("chat_update.json", %{chat_message_reference: cm_ref}, topic) do # Explicitly giving the cmr for the object here, so we don't accidentally # send a later 'last_message' that was inserted between inserting this and # streaming it out @@ -93,6 +100,7 @@ defmodule Pleroma.Web.StreamerView do ) %{ + stream: render("stream.json", %{topic: topic}), event: "pleroma:chat_update", payload: representation @@ -101,8 +109,9 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("follow_relationships_update.json", item) do + def render("follow_relationships_update.json", item, topic) do %{ + stream: render("stream.json", %{topic: topic}), event: "pleroma:follow_relationships_update", payload: %{ @@ -123,8 +132,9 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("conversation.json", %Participation{} = participation) do + def render("conversation.json", %Participation{} = participation, topic) do %{ + stream: render("stream.json", %{topic: topic}), event: "conversation", payload: Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ @@ -150,6 +160,22 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end + def render("stream.json", %{topic: "user:pleroma_chat:" <> _}), do: ["user:pleroma_chat"] + def render("stream.json", %{topic: "user:notification:" <> _}), do: ["user:notification"] + def render("stream.json", %{topic: "user:" <> _}), do: ["user"] + def render("stream.json", %{topic: "direct:" <> _}), do: ["direct"] + def render("stream.json", %{topic: "list:" <> id}), do: ["list", id] + def render("stream.json", %{topic: "hashtag:" <> tag}), do: ["hashtag", tag] + + def render("stream.json", %{topic: "public:remote:media:" <> instance}), + do: ["public:remote:media", instance] + + def render("stream.json", %{topic: "public:remote:" <> instance}), + do: ["public:remote", instance] + + def render("stream.json", %{topic: stream}) when stream in Pleroma.Constants.public_streams(), + do: [stream] + defp maybe_error(%{error: :bad_topic}), do: %{error: "bad_topic"} defp maybe_error(%{error: :unauthorized}), do: %{error: "unauthorized"} defp maybe_error(%{error: :already_authenticated}), do: %{error: "already_authenticated"} diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 827c7b5b0..d2e98df3b 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -116,6 +116,39 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert json == view_json end + test "can subscribe to multiple streams" do + user = insert(:user) + {:ok, pid} = start_socket() + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "public"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "subscribe", "result" => "success"} + }} = decode_json(raw_json) + + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "hashtag", tag: "mew"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma.respond", + "payload" => %{"type" => "subscribe", "result" => "success"} + }} = decode_json(raw_json) + + {:ok, _activity} = CommonAPI.post(user, %{status: "nice echo chamber #mew"}) + + assert_receive {:text, raw_json}, 1_000 + assert {:ok, %{"stream" => stream1}} = Jason.decode(raw_json) + assert_receive {:text, raw_json}, 1_000 + assert {:ok, %{"stream" => stream2}} = Jason.decode(raw_json) + + streams = [stream1, stream2] + assert ["hashtag", "mew"] in streams + assert ["public"] in streams + end + test "won't double subscribe" do user = insert(:user) {:ok, pid} = start_socket() diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index da97b87d8..cc57a2989 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -246,7 +246,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user", user, oauth_token) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} refute Streamer.filtered_by_user?(user, activity) end @@ -257,7 +257,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) {:ok, announce} = CommonAPI.repeat(activity.id, user) - assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} + assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce, _} refute Streamer.filtered_by_user?(user, announce) end @@ -310,7 +310,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, %Pleroma.Activity{data: _data, local: false} = announce} = Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data) - assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} + assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce, _} refute Streamer.filtered_by_user?(user, announce) end @@ -322,7 +322,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.stream("user", notify) - assert_receive {:render_with_user, _, _, ^notify} + assert_receive {:render_with_user, _, _, ^notify, _} refute Streamer.filtered_by_user?(user, notify) end @@ -334,7 +334,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.stream("user:notification", notify) - assert_receive {:render_with_user, _, _, ^notify} + assert_receive {:render_with_user, _, _, ^notify, _} refute Streamer.filtered_by_user?(user, notify) end @@ -355,7 +355,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user:pleroma_chat", user, oauth_token) Streamer.stream("user:pleroma_chat", {user, cm_ref}) - text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) + text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, "user:pleroma_chat:#{user.id}") assert text =~ "hey cirno" assert_receive {:text, ^text} @@ -373,7 +373,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.stream("user", {user, cm_ref}) - text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}) + text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, "user:#{user.id}") assert text =~ "hey cirno" assert_receive {:text, ^text} @@ -394,7 +394,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.stream("user:notification", notify) - assert_receive {:render_with_user, _, _, ^notify} + assert_receive {:render_with_user, _, _, ^notify, _} refute Streamer.filtered_by_user?(user, notify) end @@ -440,7 +440,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) - assert_receive {:render_with_user, _, "notification.json", notif} + assert_receive {:render_with_user, _, "notification.json", notif, _} assert notif.activity.id == favorite_activity.id refute Streamer.filtered_by_user?(user, notif) end @@ -469,7 +469,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) {:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user) - assert_receive {:render_with_user, _, "notification.json", notif} + assert_receive {:render_with_user, _, "notification.json", notif, _} assert notif.activity.id == follow_activity.id refute Streamer.filtered_by_user?(user, notif) end @@ -534,7 +534,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, edited} = CommonAPI.update(sender, activity, %{status: "mew mew"}) create = Pleroma.Activity.get_create_by_object_ap_id_with_object(activity.object.data["id"]) - assert_receive {:render_with_user, _, "status_update.json", ^create} + assert_receive {:render_with_user, _, "status_update.json", ^create, _} refute Streamer.filtered_by_user?(user, edited) end @@ -545,7 +545,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, edited} = CommonAPI.update(user, activity, %{status: "mew mew"}) create = Pleroma.Activity.get_create_by_object_ap_id_with_object(activity.object.data["id"]) - assert_receive {:render_with_user, _, "status_update.json", ^create} + assert_receive {:render_with_user, _, "status_update.json", ^create, _} refute Streamer.filtered_by_user?(user, edited) end end @@ -558,7 +558,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("public", user, oauth_token) {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} refute Streamer.filtered_by_user?(other_user, activity) end @@ -658,7 +658,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} assert Streamer.filtered_by_user?(user, activity) end @@ -680,7 +680,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} refute Streamer.filtered_by_user?(user, activity) end @@ -703,7 +703,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.stream("public", activity) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} refute Streamer.filtered_by_user?(user, activity) end end @@ -717,7 +717,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("public", user, oauth_token) {:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"}) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} assert Streamer.filtered_by_user?(user, activity) end @@ -734,17 +734,17 @@ defmodule Pleroma.Web.StreamerTest do {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"}) - assert_receive {:render_with_user, _, _, ^activity_one} + assert_receive {:render_with_user, _, _, ^activity_one, _} assert Streamer.filtered_by_user?(blocker, activity_one) {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"}) - assert_receive {:render_with_user, _, _, ^activity_two} + assert_receive {:render_with_user, _, _, ^activity_two, _} assert Streamer.filtered_by_user?(blocker, activity_two) {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"}) - assert_receive {:render_with_user, _, _, ^activity_three} + assert_receive {:render_with_user, _, _, ^activity_three, _} assert Streamer.filtered_by_user?(blocker, activity_three) end end @@ -805,7 +805,7 @@ defmodule Pleroma.Web.StreamerTest do visibility: "private" }) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} refute Streamer.filtered_by_user?(user_a, activity) end end @@ -823,7 +823,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2) - assert_receive {:render_with_user, _, _, ^announce_activity} + assert_receive {:render_with_user, _, _, ^announce_activity, _} assert Streamer.filtered_by_user?(user1, announce_activity) end @@ -839,7 +839,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2) - assert_receive {:render_with_user, _, "notification.json", notif} + assert_receive {:render_with_user, _, "notification.json", notif, _} assert Streamer.filtered_by_user?(user1, notif) end @@ -855,7 +855,7 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user", user1, user1_token) {:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id) - assert_receive {:render_with_user, _, "notification.json", notif} + assert_receive {:render_with_user, _, "notification.json", notif, _} refute Streamer.filtered_by_user?(user1, notif) end end @@ -870,7 +870,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) {:ok, _} = CommonAPI.add_mute(user2, activity) - assert_receive {:render_with_user, _, _, ^activity} + assert_receive {:render_with_user, _, _, ^activity, _} assert Streamer.filtered_by_user?(user2, activity) end end @@ -912,7 +912,7 @@ defmodule Pleroma.Web.StreamerTest do }) create_activity_id = create_activity.id - assert_receive {:render_with_user, _, _, ^create_activity} + assert_receive {:render_with_user, _, _, ^create_activity, _} assert_receive {:text, received_conversation1} assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) @@ -947,8 +947,8 @@ defmodule Pleroma.Web.StreamerTest do visibility: "direct" }) - assert_receive {:render_with_user, _, _, ^create_activity} - assert_receive {:render_with_user, _, _, ^create_activity2} + assert_receive {:render_with_user, _, _, ^create_activity, _} + assert_receive {:render_with_user, _, _, ^create_activity2, _} assert_receive {:text, received_conversation1} assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) assert_receive {:text, received_conversation1} @@ -977,7 +977,7 @@ defmodule Pleroma.Web.StreamerTest do receive do {StreamerTest, :ready} -> - assert_receive {:render_with_user, _, "update.json", _} + assert_receive {:render_with_user, _, "update.json", _, _} receive do {StreamerTest, :revoked} -> finalize.() From a348c2e4dd0551a603509501d718d9e0b995be90 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 01:29:11 -0400 Subject: [PATCH 0381/1008] Use pleroma: instead of pleroma. for ws events --- .../web/mastodon_api/websocket_handler.ex | 8 ++-- lib/pleroma/web/views/streamer_view.ex | 2 +- .../integration/mastodon_websocket_test.exs | 46 ++++++++++--------- test/pleroma/web/streamer_test.exs | 14 +++++- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 2707673ba..07c2b62e3 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -215,7 +215,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end defp handle_client_event( - %{"type" => "pleroma.authenticate", "token" => access_token} = _params, + %{"type" => "pleroma:authenticate", "token" => access_token} = _params, state ) do with {:auth, nil, nil} <- {:auth, state.user, state.oauth_token}, @@ -223,7 +223,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {[ {:text, StreamerView.render("pleroma_respond.json", %{ - type: "pleroma.authenticate", + type: "pleroma:authenticate", result: "success" })} ], %{state | user: user, oauth_token: oauth_token}} @@ -232,7 +232,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {[ {:text, StreamerView.render("pleroma_respond.json", %{ - type: "pleroma.authenticate", + type: "pleroma:authenticate", result: "error", error: :already_authenticated })} @@ -242,7 +242,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {[ {:text, StreamerView.render("pleroma_respond.json", %{ - type: "pleroma.authenticate", + type: "pleroma:authenticate", result: "error", error: :unauthorized })} diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index f591da9a6..f97570b0a 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -148,7 +148,7 @@ defmodule Pleroma.Web.StreamerView do def render("pleroma_respond.json", %{type: type, result: result} = params) do %{ - event: "pleroma.respond", + event: "pleroma:respond", payload: %{ result: result, diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index d2e98df3b..21e7ca2b0 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -95,7 +95,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "subscribe", "result" => "success"} }} = decode_json(raw_json) @@ -124,16 +124,20 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "subscribe", "result" => "success"} }} = decode_json(raw_json) - WebsocketClient.send_text(pid, %{type: "subscribe", stream: "hashtag", tag: "mew"} |> Jason.encode!()) + WebsocketClient.send_text( + pid, + %{type: "subscribe", stream: "hashtag", tag: "mew"} |> Jason.encode!() + ) + assert_receive {:text, raw_json}, 1_000 assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "subscribe", "result" => "success"} }} = decode_json(raw_json) @@ -157,7 +161,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "subscribe", "result" => "success"} }} = decode_json(raw_json) @@ -166,7 +170,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "subscribe", "result" => "ignored"} }} = decode_json(raw_json) @@ -184,7 +188,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "subscribe", "result" => "success"} }} = decode_json(raw_json) @@ -193,7 +197,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "unsubscribe", "result" => "success"} }} = decode_json(raw_json) @@ -262,15 +266,15 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do WebsocketClient.send_text( pid, - %{type: "pleroma.authenticate", token: token.token} |> Jason.encode!() + %{type: "pleroma:authenticate", token: token.token} |> Jason.encode!() ) assert_receive {:text, raw_json}, 1_000 assert {:ok, %{ - "event" => "pleroma.respond", - "payload" => %{"type" => "pleroma.authenticate", "result" => "success"} + "event" => "pleroma:respond", + "payload" => %{"type" => "pleroma:authenticate", "result" => "success"} }} = decode_json(raw_json) WebsocketClient.send_text(pid, %{type: "subscribe", stream: "user"} |> Jason.encode!()) @@ -278,7 +282,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{"type" => "subscribe", "result" => "success"} }} = decode_json(raw_json) end @@ -288,16 +292,16 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do WebsocketClient.send_text( pid, - %{type: "pleroma.authenticate", token: "Something else"} |> Jason.encode!() + %{type: "pleroma:authenticate", token: "Something else"} |> Jason.encode!() ) assert_receive {:text, raw_json}, 1_000 assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{ - "type" => "pleroma.authenticate", + "type" => "pleroma:authenticate", "result" => "error", "error" => "unauthorized" } @@ -309,29 +313,29 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do WebsocketClient.send_text( pid, - %{type: "pleroma.authenticate", token: token.token} |> Jason.encode!() + %{type: "pleroma:authenticate", token: token.token} |> Jason.encode!() ) assert_receive {:text, raw_json}, 1_000 assert {:ok, %{ - "event" => "pleroma.respond", - "payload" => %{"type" => "pleroma.authenticate", "result" => "success"} + "event" => "pleroma:respond", + "payload" => %{"type" => "pleroma:authenticate", "result" => "success"} }} = decode_json(raw_json) WebsocketClient.send_text( pid, - %{type: "pleroma.authenticate", token: "Something else"} |> Jason.encode!() + %{type: "pleroma:authenticate", token: "Something else"} |> Jason.encode!() ) assert_receive {:text, raw_json}, 1_000 assert {:ok, %{ - "event" => "pleroma.respond", + "event" => "pleroma:respond", "payload" => %{ - "type" => "pleroma.authenticate", + "type" => "pleroma:authenticate", "result" => "error", "error" => "already_authenticated" } diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index cc57a2989..d85358fd4 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -355,7 +355,12 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user:pleroma_chat", user, oauth_token) Streamer.stream("user:pleroma_chat", {user, cm_ref}) - text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, "user:pleroma_chat:#{user.id}") + text = + StreamerView.render( + "chat_update.json", + %{chat_message_reference: cm_ref}, + "user:pleroma_chat:#{user.id}" + ) assert text =~ "hey cirno" assert_receive {:text, ^text} @@ -373,7 +378,12 @@ defmodule Pleroma.Web.StreamerTest do Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.stream("user", {user, cm_ref}) - text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref}, "user:#{user.id}") + text = + StreamerView.render( + "chat_update.json", + %{chat_message_reference: cm_ref}, + "user:#{user.id}" + ) assert text =~ "hey cirno" assert_receive {:text, ^text} From 2d430679468a4c6a9b5c365a53f007cfa28679d9 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 02:24:30 -0400 Subject: [PATCH 0382/1008] Document the streaming endpoint --- .../API/differences_in_mastoapi_responses.md | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 4007c63c8..ef6b3b3be 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -357,6 +357,122 @@ The message payload consist of: - `follower_count`: follower count - `following_count`: following count +### Authenticating via `sec-websocket-protocol` header + +Pleroma allows to authenticate via the `sec-websocket-protocol` header, for example, if your access token is `your-access-token`, you can authenticate using the following: + +``` +sec-websocket-protocol: your-access-token +``` + +### Authenticating after connection via `pleroma:authenticate` event + +Pleroma allows to authenticate after connection is established, via the `pleroma:authenticate` event. For example, if your access token is `your-access-token`, you can send the following after the connection is established: + +``` +{"type": "pleroma:authenticate", "token": "your-access-token"} +``` + +### Response to client-sent events + +Pleroma will respond to client-sent events that it recognizes. Supported event types are: + +- `subscribe` +- `unsubscribe` +- `pleroma:authenticate` + +The reply will be in the following format: + +``` +{ + "event": "pleroma:respond", + "payload": "{\"type\": \"\", \"result\": \"\", \"error\": \"\"}" +} +``` + +Result of the action can be either `success`, `ignored` or `error`. If it is `error`, the `error` property will contain the error code. Otherwise, the `error` property will not be present. Below are some examples: + +``` +{ + "event": "pleroma:respond", + "payload": "{\"type\": \"pleroma:authenticate\", \"result\": \"success\"}" +} + +{ + "event": "pleroma:respond", + "payload": "{\"type\": \"subscribe\", \"result\": \"ignored\"}" +} + +{ + "event": "pleroma:respond", + "payload": "{\"type\": \"unsubscribe\", \"result\": \"error\", \"error\": \"bad_topic\"}" +} +``` + +If the sent event is not of a type that Pleroma supports, it will not reply. + +### The `stream` attribute of a server-sent event + +Technically, this is in Mastodon, but its documentation does nothing to specify its format. + +This attribute appears on every event type except `pleroma:respond` and `delete`. It helps clients determine where they should display the new statuses. + +The value of the attribute is an array containing one or two elements. The first element is the type of the stream. The second is the identifier related to that specific stream, if applicable. + +For the following stream types, there is a second element in the array: + +- `list`: The second element is the id of the list. +- `hashtag`: The second element is the name of the hashtag. +- `public:remote:media` and `public:remote`: The second element is the domain of the corresponding instance. + +For all other stream types, there is no second element. + +Some examples of valid `stream` values: + +- `list:1`: List of id 1. +- `hashtag:mew`: The hashtag #mew. +- `user:notifications`: Notifications for the current user. +- `user`: Home timeline. +- `public:remote:mew.moe`: Public posts from the instance mew.moe . + +### The unified streaming endpoint + +If you do not specify a stream to connect to when requesting `/api/v1/streaming`, you will enter a connection that subscribes to no streams. After the connection is established, you can authenticate and then subscribe to different streams. + +### List of supported streams + +Below is a list of supported streams by Pleroma. To make a single-stream WebSocket connection, append the string specified in "Query style" to the streaming endpoint url. +To subscribe to a stream after the connection is established, merge the JSON object specified in "Subscribe style" with `{"type": "subscribe"}`. To unsubscribe, merge it with `{"type": "unsubscribe"}`. + +For example, to receive updates on the list 1, you can connect to `/api/v1/streaming/?stream=list&list=1`, or send + +``` +{"type": "subscribe", "stream": "list", "list": 1} +``` + +upon establishing the websocket connection. + +To unsubscribe to list 1, send + +``` +{"type": "unsubscribe", "stream": "list", "list": 1} +``` + +Note that if you specify a stream that requires a logged-in user in the query string (for example, `user` or `list`), you have to specify the access token when you are trying to establish the connection, i.e. in the query string or via the `sec-websocket-protocol` header. + +- `list` + - Query style: `?stream=list&list=` + - Subscribe style: `{"stream": "list", "list": }` +- `public`, `public:local`, `public:media`, `public:local:media`, `user`, `user:pleroma_chat`, `user:notifications`, `direct` + - Query style: `?stream=` + - Subscribe style: `{"stream": ""}` +- `hashtag` + - Query style: `?stream=hashtag&tag=` + - Subscribe style: `{"stream": "hashtag", "tag": ""}` +- `public:remote`, `public:remote:media` + - Query style: `?stream=&instance=` + - Subscribe style: `{"stream": "", "instance": ""}` + ## User muting and thread muting Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds. From 9572be1e5fe0201cd069271c7025ef233e8ddf00 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 02:35:12 -0400 Subject: [PATCH 0383/1008] Add tests for list streams --- .../integration/mastodon_websocket_test.exs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 21e7ca2b0..8129645ec 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -342,6 +342,43 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do }} = decode_json(raw_json) end + test "accepts the 'list' stream", %{token: token, user: user} do + posting_user = insert(:user) + + {:ok, list} = Pleroma.List.create("test", user) + Pleroma.List.follow(list, posting_user) + + assert {:ok, _} = start_socket("?stream=list&access_token=#{token.token}&list=#{list.id}") + + assert {:ok, pid} = start_socket("?access_token=#{token.token}") + + WebsocketClient.send_text( + pid, + %{type: "subscribe", stream: "list", list: list.id} |> Jason.encode!() + ) + + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma:respond", + "payload" => %{"type" => "subscribe", "result" => "success"} + }} = decode_json(raw_json) + + WebsocketClient.send_text( + pid, + %{type: "subscribe", stream: "list", list: to_string(list.id)} |> Jason.encode!() + ) + + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma:respond", + "payload" => %{"type" => "subscribe", "result" => "ignored"} + }} = decode_json(raw_json) + end + test "disconnect when token is revoked", %{app: app, user: user, token: token} do assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}") assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}") From 7f12ad6dccfe4c81fa7e0d4e66c43bedadbf4c6a Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 02:37:19 -0400 Subject: [PATCH 0384/1008] Fix docs wording --- .../API/differences_in_mastoapi_responses.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index ef6b3b3be..48a9c104c 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -421,7 +421,7 @@ The value of the attribute is an array containing one or two elements. The first For the following stream types, there is a second element in the array: -- `list`: The second element is the id of the list. +- `list`: The second element is the id of the list, as a string. - `hashtag`: The second element is the name of the hashtag. - `public:remote:media` and `public:remote`: The second element is the domain of the corresponding instance. @@ -429,11 +429,11 @@ For all other stream types, there is no second element. Some examples of valid `stream` values: -- `list:1`: List of id 1. -- `hashtag:mew`: The hashtag #mew. -- `user:notifications`: Notifications for the current user. -- `user`: Home timeline. -- `public:remote:mew.moe`: Public posts from the instance mew.moe . +- `["list", "1"]`: List of id 1. +- `["hashtag", "mew"]`: The hashtag #mew. +- `["user:notifications"]`: Notifications for the current user. +- `["user"]`: Home timeline. +- `["public:remote", "mew.moe"]`: Public posts from the instance mew.moe . ### The unified streaming endpoint @@ -447,7 +447,7 @@ To subscribe to a stream after the connection is established, merge the JSON obj For example, to receive updates on the list 1, you can connect to `/api/v1/streaming/?stream=list&list=1`, or send ``` -{"type": "subscribe", "stream": "list", "list": 1} +{"type": "subscribe", "stream": "list", "list": "1"} ``` upon establishing the websocket connection. @@ -455,14 +455,14 @@ upon establishing the websocket connection. To unsubscribe to list 1, send ``` -{"type": "unsubscribe", "stream": "list", "list": 1} +{"type": "unsubscribe", "stream": "list", "list": "1"} ``` Note that if you specify a stream that requires a logged-in user in the query string (for example, `user` or `list`), you have to specify the access token when you are trying to establish the connection, i.e. in the query string or via the `sec-websocket-protocol` header. - `list` - Query style: `?stream=list&list=` - - Subscribe style: `{"stream": "list", "list": }` + - Subscribe style: `{"stream": "list", "list": ""}` - `public`, `public:local`, `public:media`, `public:local:media`, `user`, `user:pleroma_chat`, `user:notifications`, `direct` - Query style: `?stream=` - Subscribe style: `{"stream": ""}` From 949c4f01c6d5686ff1852e6439616c2069557ef0 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 02:38:16 -0400 Subject: [PATCH 0385/1008] Fix NotificationTest --- test/pleroma/notification_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index e55aa3a08..71af9acb8 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -252,7 +252,7 @@ defmodule Pleroma.NotificationTest do task = Task.async(fn -> {:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token) - assert_receive {:render_with_user, _, _, _}, 4_000 + assert_receive {:render_with_user, _, _, _, _}, 4_000 end) task_user_notification = @@ -260,7 +260,7 @@ defmodule Pleroma.NotificationTest do {:ok, _topic} = Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) - assert_receive {:render_with_user, _, _, _}, 4_000 + assert_receive {:render_with_user, _, _, _, _}, 4_000 end) activity = insert(:note_activity) From eebc605bc25deead55c305d703c06ddb9d9b1107 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 08:27:43 -0400 Subject: [PATCH 0386/1008] Clear up debug statement --- lib/pleroma/web/streamer.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 70e46617a..48ca82421 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -357,7 +357,6 @@ defmodule Pleroma.Web.Streamer do end defp push_to_socket(topic, item) do - Logger.debug("topic=#{topic}") anon_render = StreamerView.render("update.json", item, topic) Registry.dispatch(@registry, topic, fn list -> From 050227f11898f402f5888d53e6460b704bcd0a8b Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 08:39:38 -0400 Subject: [PATCH 0387/1008] Add test to cover error: bad_topic --- test/pleroma/integration/mastodon_websocket_test.exs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 8129645ec..0e8413749 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -180,6 +180,18 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do refute_receive {:text, _}, 1_000 end + test "rejects invalid streams" do + {:ok, pid} = start_socket() + WebsocketClient.send_text(pid, %{type: "subscribe", stream: "nonsense"} |> Jason.encode!()) + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "pleroma:respond", + "payload" => %{"type" => "subscribe", "result" => "error", "error" => "bad_topic"} + }} = decode_json(raw_json) + end + test "can unsubscribe" do user = insert(:user) {:ok, pid} = start_socket() From 4cf109d3c4093d788e6b2de229a9e4034146a5be Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 08:47:46 -0400 Subject: [PATCH 0388/1008] Add test to cover rendering update with user --- .../integration/mastodon_websocket_test.exs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 0e8413749..5ec681ce3 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -406,5 +406,32 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert_receive {:close, _} refute_receive {:close, _} end + + test "receives private statuses", %{user: reading_user, token: token} do + user = insert(:user) + CommonAPI.follow(reading_user, user) + + {:ok, _} = start_socket("?stream=user&access_token=#{token.token}") + + {:ok, activity} = + CommonAPI.post(user, %{status: "nice echo chamber", visibility: "private"}) + + assert_receive {:text, raw_json}, 1_000 + assert {:ok, json} = Jason.decode(raw_json) + + assert "update" == json["event"] + assert json["payload"] + assert {:ok, json} = Jason.decode(json["payload"]) + + view_json = + Pleroma.Web.MastodonAPI.StatusView.render("show.json", + activity: activity, + for: reading_user + ) + |> Jason.encode!() + |> Jason.decode!() + + assert json == view_json + end end end From 26f5caebae1581f1dcdc6d1352840d27009e061c Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 08:56:53 -0400 Subject: [PATCH 0389/1008] Add test to cover notifications streaming --- .../integration/mastodon_websocket_test.exs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 5ec681ce3..96648264e 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -433,5 +433,30 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert json == view_json end + + test "receives notifications", %{user: reading_user, token: token} do + user = insert(:user) + CommonAPI.follow(reading_user, user) + + {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}") + + {:ok, %Pleroma.Activity{id: activity_id} = _activity} = + CommonAPI.post(user, %{ + status: "nice echo chamber @#{reading_user.nickname}", + visibility: "private" + }) + + assert_receive {:text, raw_json}, 1_000 + + assert {:ok, + %{ + "event" => "notification", + "payload" => %{ + "status" => %{ + "id" => ^activity_id + } + } + }} = decode_json(raw_json) + end end end From 314360e5e3a4bdd13f152e94050cb8562e2ea0ed Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 09:02:43 -0400 Subject: [PATCH 0390/1008] Add test to cover edit streaming --- .../integration/mastodon_websocket_test.exs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 96648264e..7d5dfc255 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -434,6 +434,34 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do assert json == view_json end + test "receives edits", %{user: reading_user, token: token} do + user = insert(:user) + CommonAPI.follow(reading_user, user) + + {:ok, _} = start_socket("?stream=user&access_token=#{token.token}") + + {:ok, activity} = + CommonAPI.post(user, %{status: "nice echo chamber", visibility: "private"}) + + assert_receive {:text, _raw_json}, 1_000 + + {:ok, _} = CommonAPI.update(user, activity, %{status: "mew mew", visibility: "private"}) + + assert_receive {:text, raw_json}, 1_000 + + activity = Pleroma.Activity.normalize(activity) + + view_json = + Pleroma.Web.MastodonAPI.StatusView.render("show.json", + activity: activity, + for: reading_user + ) + |> Jason.encode!() + |> Jason.decode!() + + assert {:ok, %{"event" => "status.update", "payload" => ^view_json}} = decode_json(raw_json) + end + test "receives notifications", %{user: reading_user, token: token} do user = insert(:user) CommonAPI.follow(reading_user, user) From 844d1a14e0b4aabbb61a6693fa6dd3a0aa0dbc5b Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 15:31:56 -0400 Subject: [PATCH 0391/1008] Start writing api docs for streaming endpoint --- lib/pleroma/web/api_spec.ex | 10 +- .../operations/streaming_operation.ex | 186 ++++++++++++++++++ 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 lib/pleroma/web/api_spec/operations/streaming_operation.ex diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 2d56dc643..163226ce5 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -10,6 +10,14 @@ defmodule Pleroma.Web.ApiSpec do @behaviour OpenApi + defp streaming_paths do + %{ + "/api/v1/streaming" => %OpenApiSpex.PathItem{ + get: Pleroma.Web.ApiSpec.StreamingOperation.streaming_operation() + } + } + end + @impl OpenApi def spec(opts \\ []) do %OpenApi{ @@ -45,7 +53,7 @@ defmodule Pleroma.Web.ApiSpec do } }, # populate the paths from a phoenix router - paths: OpenApiSpex.Paths.from_router(Router), + paths: Map.merge(streaming_paths(), OpenApiSpex.Paths.from_router(Router)), components: %OpenApiSpex.Components{ parameters: %{ "accountIdOrNickname" => diff --git a/lib/pleroma/web/api_spec/operations/streaming_operation.ex b/lib/pleroma/web/api_spec/operations/streaming_operation.ex new file mode 100644 index 000000000..1ef7d72ef --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/streaming_operation.ex @@ -0,0 +1,186 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.StreamingOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Response + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.Status + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + @spec streaming_operation() :: Operation.t() + def streaming_operation do + %Operation{ + tags: ["Timelines"], + summary: "Establish streaming connection", + description: "Receive statuses in real-time via WebSocket.", + security: [%{"oAuth" => ["read:statuses", "read:notifications"]}], + parameters: [ + Operation.parameter(:connection, :header, %Schema{type: :string}, "connection header", + required: true + ), + Operation.parameter(:upgrade, :header, %Schema{type: :string}, "upgrade header", + required: true + ), + Operation.parameter( + :"sec-websocket-key", + :header, + %Schema{type: :string}, + "sec-websocket-key header", + required: true + ), + Operation.parameter( + :"sec-websocket-version", + :header, + %Schema{type: :string}, + "sec-websocket-version header", + required: true + ) + ], + responses: %{ + 101 => switching_protocols_response(), + 200 => + Operation.response( + "Server-sent events", + "application/json", + server_sent_events() + ) + } + } + end + + defp switching_protocols_response do + %Response{ + description: "Switching protocols", + headers: %{ + "connection" => %OpenApiSpex.Header{required: true}, + "upgrade" => %OpenApiSpex.Header{required: true}, + "sec-websocket-accept" => %OpenApiSpex.Header{required: true} + } + } + end + + defp server_sent_events do + %Schema{ + oneOf: [ + update_event(), + status_update_event(), + pleroma_respond_event() + ] + } + end + + defp stream do + %Schema{ + type: :array, + title: "Stream", + description: """ + The stream identifier. + The first item is the name of the stream. If the stream needs a differentiator, the second item will be the corresponding identifier. + Currently, for the following stream types, there is a second element in the array: + + - `list`: The second element is the id of the list, as a string. + - `hashtag`: The second element is the name of the hashtag. + - `public:remote:media` and `public:remote`: The second element is the domain of the corresponding instance. + """, + maxItems: 2, + minItems: 1, + items: %Schema{type: :string}, + example: ["hashtag", "mew"] + } + end + + defp get_schema(%Schema{} = schema), do: schema + defp get_schema(schema), do: schema.schema + + defp server_sent_event_helper(name, description, type, payload, opts \\ []) do + payload_type = opts[:payload_type] || :json + has_stream = opts[:has_stream] || true + + stream_properties = + if has_stream do + %{stream: stream()} + else + %{} + end + + stream_example = if has_stream, do: %{"stream" => get_schema(stream()).example}, else: %{} + + stream_required = if has_stream, do: [:stream], else: [] + + %Schema{ + type: :object, + title: name, + description: description, + required: [:event, :payload] ++ stream_required, + properties: + %{ + event: %Schema{ + title: "Event type", + description: "Type of the event.", + type: :string, + required: true, + enum: [type] + }, + payload: + if payload_type == :json do + %Schema{ + title: "Event payload", + description: "JSON-encoded string of #{get_schema(payload).title}", + allOf: [payload] + } + else + payload + end + } + |> Map.merge(stream_properties), + example: + %{ + "event" => type, + "payload" => get_schema(payload).example |> Jason.encode!() + } + |> Map.merge(stream_example) + } + end + + defp update_event do + server_sent_event_helper("New status", "A newly-posted status.", "update", Status) + end + + defp status_update_event do + server_sent_event_helper("Edit", "A status that was just edited", "status.update", Status) + end + + defp pleroma_respond_event do + server_sent_event_helper( + "Server response", + "A response to a client-sent event.", + "pleroma:respond", + %Schema{ + type: :object, + title: "Results", + required: [:result], + properties: %{ + result: %Schema{ + type: :string, + title: "Result of the request", + enum: ["success", "error", "ignored"] + }, + error: %Schema{ + type: :string, + title: "Error code", + description: "An error identifier. Only appears if `result` is `error`." + } + }, + example: %{"result" => "success"} + } + ) + end +end From dcef33f5f0c93883a634d12dc662b83d7ef6abfa Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 16:07:27 -0400 Subject: [PATCH 0392/1008] Document server-sent events of streaming --- .../operations/streaming_operation.ex | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/lib/pleroma/web/api_spec/operations/streaming_operation.ex b/lib/pleroma/web/api_spec/operations/streaming_operation.ex index 1ef7d72ef..cdef41603 100644 --- a/lib/pleroma/web/api_spec/operations/streaming_operation.ex +++ b/lib/pleroma/web/api_spec/operations/streaming_operation.ex @@ -7,6 +7,10 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do alias OpenApiSpex.Response alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.NotificationOperation + alias Pleroma.Web.ApiSpec.Schemas.Chat + alias Pleroma.Web.ApiSpec.Schemas.Conversation + alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Status @spec open_api_operation(atom) :: Operation.t() @@ -22,6 +26,7 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do summary: "Establish streaming connection", description: "Receive statuses in real-time via WebSocket.", security: [%{"oAuth" => ["read:statuses", "read:notifications"]}], + operationId: "WebsocketHandler.streaming", parameters: [ Operation.parameter(:connection, :header, %Schema{type: :string}, "connection header", required: true @@ -72,6 +77,11 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do oneOf: [ update_event(), status_update_event(), + notification_event(), + chat_update_event(), + follow_relationships_update_event(), + conversation_event(), + delete_event(), pleroma_respond_event() ] } @@ -158,6 +168,102 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do server_sent_event_helper("Edit", "A status that was just edited", "status.update", Status) end + defp notification_event do + server_sent_event_helper( + "Notification", + "A new notification.", + "notification", + NotificationOperation.notification() + ) + end + + defp follow_relationships_update_event do + server_sent_event_helper( + "Follow relationships update", + "An update to follow relationships.", + "pleroma:follow_relationships_update", + %Schema{ + type: :object, + title: "Follow relationships update", + required: [:state, :follower, :following], + properties: %{ + state: %Schema{ + type: :string, + description: "Follow state of the relationship.", + enum: ["follow_pending", "follow_accept", "follow_reject", "unfollow"] + }, + follower: %Schema{ + type: :object, + description: "Information about the follower.", + required: [:id, :follower_count, :following_count], + properties: %{ + id: FlakeID, + follower_count: %Schema{type: :integer}, + following_count: %Schema{type: :integer} + } + }, + following: %Schema{ + type: :object, + description: "Information about the following person.", + required: [:id, :follower_count, :following_count], + properties: %{ + id: FlakeID, + follower_count: %Schema{type: :integer}, + following_count: %Schema{type: :integer} + } + } + }, + example: %{ + "state" => "follow_pending", + "follower" => %{ + "id" => "someUser1", + "follower_count" => 1, + "following_count" => 1 + }, + "following" => %{ + "id" => "someUser2", + "follower_count" => 1, + "following_count" => 1 + } + } + } + ) + end + + defp chat_update_event do + server_sent_event_helper( + "Chat update", + "A new chat message.", + "pleroma:chat_update", + Chat + ) + end + + defp conversation_event do + server_sent_event_helper( + "Conversation", + "An update about a conversation", + "conversation", + Conversation + ) + end + + defp delete_event do + server_sent_event_helper( + "Delete", + "A status that was just deleted.", + "delete", + %Schema{ + type: :string, + title: "Status id", + description: "Id of the deleted status", + allOf: [FlakeID], + example: "some-opaque-id" + }, + payload_type: :string + ) + end + defp pleroma_respond_event do server_sent_event_helper( "Server response", From 8829dcaee42b3ad1ee50f95b0586b22118771785 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 16:33:22 -0400 Subject: [PATCH 0393/1008] Document client-sent events in streaming --- .../operations/streaming_operation.ex | 126 +++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/streaming_operation.ex b/lib/pleroma/web/api_spec/operations/streaming_operation.ex index cdef41603..18674c9a1 100644 --- a/lib/pleroma/web/api_spec/operations/streaming_operation.ex +++ b/lib/pleroma/web/api_spec/operations/streaming_operation.ex @@ -6,13 +6,14 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Response alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Helpers alias Pleroma.Web.ApiSpec.NotificationOperation alias Pleroma.Web.ApiSpec.Schemas.Chat alias Pleroma.Web.ApiSpec.Schemas.Conversation alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Status + require Pleroma.Constants + @spec open_api_operation(atom) :: Operation.t() def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -49,6 +50,7 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do required: true ) ], + requestBody: request_body("Client-sent events", client_sent_events()), responses: %{ 101 => switching_protocols_response(), 200 => @@ -289,4 +291,126 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do } ) end + + defp client_sent_events do + %Schema{ + oneOf: [ + subscribe_event(), + unsubscribe_event(), + authenticate_event() + ] + } + end + + defp request_body(description, schema, opts \\ []) do + %OpenApiSpex.RequestBody{ + description: description, + content: %{ + "application/json" => %OpenApiSpex.MediaType{ + schema: schema, + example: opts[:example], + examples: opts[:examples] + } + } + } + end + + defp client_sent_event_helper(name, description, type, properties, opts) do + required = opts[:required] || [] + + %Schema{ + type: :object, + title: name, + required: [:type] ++ required, + description: description, + properties: + %{ + type: %Schema{type: :string, enum: [type], description: "Type of the event."} + } + |> Map.merge(properties), + example: opts[:example] + } + end + + defp subscribe_event do + client_sent_event_helper( + "Subscribe", + "Subscribe to a stream.", + "subscribe", + stream_specifier(), + required: [:stream], + example: %{"type" => "subscribe", "stream" => "list", "list" => "1"} + ) + end + + defp unsubscribe_event do + client_sent_event_helper( + "Unsubscribe", + "Unsubscribe from a stream.", + "subscribe", + stream_specifier(), + required: [:stream], + example: %{ + "type" => "unsubscribe", + "stream" => "public:remote:media", + "instance" => "example.org" + } + ) + end + + defp authenticate_event do + client_sent_event_helper( + "Authenticate", + "Authenticate via an access token.", + "pleroma:authenticate", + %{ + token: %Schema{ + type: :string, + description: "An OAuth access token with corresponding permissions.", + example: "some token" + } + }, + required: [:token] + ) + end + + defp stream_specifier do + %{ + stream: %Schema{ + type: :string, + description: "The name of the stream.", + enum: + Pleroma.Constants.public_streams() ++ + [ + "public:remote", + "public:remote:media", + "user", + "user:pleroma_chat", + "user:notification", + "direct", + "list", + "hashtag" + ] + }, + list: %Schema{ + type: :string, + title: "List id", + description: "The id of the list. Required when `stream` is `list`.", + example: "some-id" + }, + tag: %Schema{ + type: :string, + title: "Hashtag name", + description: "The name of the hashtag. Required when `stream` is `hashtag`.", + example: "mew" + }, + instance: %Schema{ + type: :string, + title: "Domain name", + description: + "Domain name of the instance. Required when `stream` is `public:remote` or `public:remote:media`.", + example: "example.org" + } + } + end end From f393a15dd1217a7f6aec9e9acc7b983e7b165a91 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 16:46:32 -0400 Subject: [PATCH 0394/1008] Fix some specs about server-sent events in streaming --- .../operations/streaming_operation.ex | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/streaming_operation.ex b/lib/pleroma/web/api_spec/operations/streaming_operation.ex index 18674c9a1..fa48b9613 100644 --- a/lib/pleroma/web/api_spec/operations/streaming_operation.ex +++ b/lib/pleroma/web/api_spec/operations/streaming_operation.ex @@ -113,8 +113,8 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do defp get_schema(schema), do: schema.schema defp server_sent_event_helper(name, description, type, payload, opts \\ []) do - payload_type = opts[:payload_type] || :json - has_stream = opts[:has_stream] || true + payload_type = Keyword.get(opts, :payload_type, :json) + has_stream = Keyword.get(opts, :has_stream, true) stream_properties = if has_stream do @@ -127,6 +127,24 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do stream_required = if has_stream, do: [:stream], else: [] + payload_schema = + if payload_type == :json do + %Schema{ + title: "Event payload", + description: "JSON-encoded string of #{get_schema(payload).title}", + allOf: [payload] + } + else + payload + end + + payload_example = + if payload_type == :json do + get_schema(payload).example |> Jason.encode!() + else + get_schema(payload).example + end + %Schema{ type: :object, title: name, @@ -141,22 +159,13 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do required: true, enum: [type] }, - payload: - if payload_type == :json do - %Schema{ - title: "Event payload", - description: "JSON-encoded string of #{get_schema(payload).title}", - allOf: [payload] - } - else - payload - end + payload: payload_schema } |> Map.merge(stream_properties), example: %{ "event" => type, - "payload" => get_schema(payload).example |> Jason.encode!() + "payload" => payload_example } |> Map.merge(stream_example) } @@ -262,7 +271,8 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do allOf: [FlakeID], example: "some-opaque-id" }, - payload_type: :string + payload_type: :string, + has_stream: false ) end @@ -274,7 +284,7 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do %Schema{ type: :object, title: "Results", - required: [:result], + required: [:result, :type], properties: %{ result: %Schema{ type: :string, @@ -285,10 +295,15 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do type: :string, title: "Error code", description: "An error identifier. Only appears if `result` is `error`." + }, + type: %Schema{ + type: :string, + description: "Type of the request." } }, - example: %{"result" => "success"} - } + example: %{"result" => "success", "type" => "pleroma:authenticate"} + }, + has_stream: false ) end From c13f0a8460853d8fe9aa04cb5d57ea06b692a3bf Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 17:00:35 -0400 Subject: [PATCH 0395/1008] Add meta-info and query strings to streaming doc --- .../operations/streaming_operation.ex | 89 +++++++++++++------ 1 file changed, 61 insertions(+), 28 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/streaming_operation.ex b/lib/pleroma/web/api_spec/operations/streaming_operation.ex index fa48b9613..4c4888d8e 100644 --- a/lib/pleroma/web/api_spec/operations/streaming_operation.ex +++ b/lib/pleroma/web/api_spec/operations/streaming_operation.ex @@ -25,31 +25,46 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do %Operation{ tags: ["Timelines"], summary: "Establish streaming connection", - description: "Receive statuses in real-time via WebSocket.", + description: """ + Receive statuses in real-time via WebSocket. + + You can specify the access token on the query string or through the `sec-websocket-protocol` header. Using + the query string to authenticate is considered unsafe and should not be used unless you have to (e.g. to maintain + your client's compatibility with Mastodon). + + You may specify a stream on the query string. If you do so and you are connecting to a stream that requires logged-in users, + you must specify the access token at the time of the connection (i.e. via query string or header). + + Otherwise, you have the option to authenticate after you have established the connection through client-sent events. + + The "Request body" section below describes what events clients can send through WebSocket, and the "Responses" section + describes what events server will send through WebSocket. + """, security: [%{"oAuth" => ["read:statuses", "read:notifications"]}], operationId: "WebsocketHandler.streaming", - parameters: [ - Operation.parameter(:connection, :header, %Schema{type: :string}, "connection header", - required: true - ), - Operation.parameter(:upgrade, :header, %Schema{type: :string}, "upgrade header", - required: true - ), - Operation.parameter( - :"sec-websocket-key", - :header, - %Schema{type: :string}, - "sec-websocket-key header", - required: true - ), - Operation.parameter( - :"sec-websocket-version", - :header, - %Schema{type: :string}, - "sec-websocket-version header", - required: true - ) - ], + parameters: + [ + Operation.parameter(:connection, :header, %Schema{type: :string}, "connection header", + required: true + ), + Operation.parameter(:upgrade, :header, %Schema{type: :string}, "upgrade header", + required: true + ), + Operation.parameter( + :"sec-websocket-key", + :header, + %Schema{type: :string}, + "sec-websocket-key header", + required: true + ), + Operation.parameter( + :"sec-websocket-version", + :header, + %Schema{type: :string}, + "sec-websocket-version header", + required: true + ) + ] ++ stream_params() ++ access_token_params(), requestBody: request_body("Client-sent events", client_sent_events()), responses: %{ 101 => switching_protocols_response(), @@ -63,6 +78,20 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do } end + defp stream_params do + stream_specifier() + |> Enum.map(fn {name, schema} -> + Operation.parameter(name, :query, schema, get_schema(schema).description) + end) + end + + defp access_token_params do + [ + Operation.parameter(:access_token, :query, token(), token().description), + Operation.parameter(:"sec-websocket-protocol", :header, token(), token().description) + ] + end + defp switching_protocols_response do %Response{ description: "Switching protocols", @@ -379,16 +408,20 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do "Authenticate via an access token.", "pleroma:authenticate", %{ - token: %Schema{ - type: :string, - description: "An OAuth access token with corresponding permissions.", - example: "some token" - } + token: token() }, required: [:token] ) end + defp token do + %Schema{ + type: :string, + description: "An OAuth access token with corresponding permissions.", + example: "some token" + } + end + defp stream_specifier do %{ stream: %Schema{ From 32de0683c4069f5877722dce0b4f5a9a6825b7c7 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 17:15:58 -0400 Subject: [PATCH 0396/1008] Fix unsubscribe event type in streaming doc --- lib/pleroma/web/api_spec/operations/streaming_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/streaming_operation.ex b/lib/pleroma/web/api_spec/operations/streaming_operation.ex index 4c4888d8e..ae3aeb4ab 100644 --- a/lib/pleroma/web/api_spec/operations/streaming_operation.ex +++ b/lib/pleroma/web/api_spec/operations/streaming_operation.ex @@ -391,7 +391,7 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do client_sent_event_helper( "Unsubscribe", "Unsubscribe from a stream.", - "subscribe", + "unsubscribe", stream_specifier(), required: [:stream], example: %{ From 840dd01035581a37613f695facdd99fbf6ac8319 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Apr 2023 18:33:43 -0400 Subject: [PATCH 0397/1008] Fix duplicated schema names --- lib/pleroma/web/api_spec/operations/streaming_operation.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/streaming_operation.ex b/lib/pleroma/web/api_spec/operations/streaming_operation.ex index ae3aeb4ab..b580bc2f0 100644 --- a/lib/pleroma/web/api_spec/operations/streaming_operation.ex +++ b/lib/pleroma/web/api_spec/operations/streaming_operation.ex @@ -281,7 +281,7 @@ defmodule Pleroma.Web.ApiSpec.StreamingOperation do defp conversation_event do server_sent_event_helper( - "Conversation", + "Conversation update", "An update about a conversation", "conversation", Conversation From 3e7d2e29b369535a9a942a4090cde9a21892f8c1 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 1 Jul 2023 23:07:07 -0400 Subject: [PATCH 0398/1008] Add changelog --- changelog.d/unified-streaming.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/unified-streaming.add diff --git a/changelog.d/unified-streaming.add b/changelog.d/unified-streaming.add new file mode 100644 index 000000000..84821fcc8 --- /dev/null +++ b/changelog.d/unified-streaming.add @@ -0,0 +1 @@ +Add unified streaming endpoint From eb33a03d0ad8571e3f0f3e0c5e9af39158c72a09 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 18 Jul 2023 18:13:49 -0400 Subject: [PATCH 0399/1008] Explain the encode-decode roundtrip --- test/pleroma/integration/mastodon_websocket_test.exs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 7d5dfc255..a2c20f0a6 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -38,6 +38,13 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do end end + # Turns atom keys to strings + defp atom_key_to_string(json) do + json + |> Jason.encode!() + |> Jason.decode!() + end + test "refuses invalid requests" do capture_log(fn -> assert {:error, %WebSockex.RequestError{code: 404}} = start_socket("?stream=ncjdk") @@ -80,8 +87,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do view_json = Pleroma.Web.MastodonAPI.StatusView.render("show.json", activity: activity, for: nil) - |> Jason.encode!() - |> Jason.decode!() + |> atom_key_to_string() assert json == view_json end From b748efe66a099b66300f2beda42f5639911bab4a Mon Sep 17 00:00:00 2001 From: tusooa Date: Sat, 29 Jul 2023 12:55:43 -0400 Subject: [PATCH 0400/1008] Fix mentioning punycode domains when using Markdown --- changelog.d/punycode-mention.fix | 1 + lib/pleroma/formatter.ex | 2 +- test/pleroma/web/common_api_test.exs | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 changelog.d/punycode-mention.fix diff --git a/changelog.d/punycode-mention.fix b/changelog.d/punycode-mention.fix new file mode 100644 index 000000000..f013c2dac --- /dev/null +++ b/changelog.d/punycode-mention.fix @@ -0,0 +1 @@ +Fix mentioning punycode domains when using Markdown diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index a46c3e381..11d5af2fb 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -124,7 +124,7 @@ defmodule Pleroma.Formatter do end def markdown_to_html(text) do - Earmark.as_html!(text, %Earmark.Options{compact_output: true}) + Earmark.as_html!(text, %Earmark.Options{compact_output: true, smartypants: false}) end def html_escape({text, mentions, hashtags}, type) do diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index a98b16d4b..b21dd4e23 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -843,6 +843,18 @@ defmodule Pleroma.Web.CommonAPITest do {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: public.id}) {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: public.id}) end + + test "it properly mentions punycode domain" do + user = insert(:user) + + _mentioned_user = + insert(:user, ap_id: "https://xn--i2raa.com/users/yyy", nickname: "yyy@xn--i2raa.com") + + {:ok, activity} = + CommonAPI.post(user, %{status: "hey @yyy@xn--i2raa.com", content_type: "text/markdown"}) + + assert "https://xn--i2raa.com/users/yyy" in Object.normalize(activity).data["to"] + end end describe "reactions" do From df0b56576a6863db9f1ca2c7987f2a465bd1f1c3 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 24 Sep 2023 22:46:30 -0400 Subject: [PATCH 0401/1008] Fix other quotation mark conversion tests --- test/pleroma/web/common_api/utils_test.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 4ce039d64..27b1da1e3 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -200,7 +200,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {result, _, []} = Utils.format_input(code, "text/markdown") assert result == - ~s[

@mario @luigi yo what’s up?

] + ~s[

@mario @luigi yo what's up?

] end test "remote mentions" do @@ -211,7 +211,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do {result, _, []} = Utils.format_input(code, "text/markdown") assert result == - ~s[

@mario @luigi yo what’s up?

] + ~s[

@mario @luigi yo what's up?

] end test "raw HTML" do @@ -229,7 +229,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do test "blockquote" do code = ~s[> whoms't are you quoting?] {result, [], []} = Utils.format_input(code, "text/markdown") - assert result == "

whoms’t are you quoting?

" + assert result == "

whoms't are you quoting?

" end test "code" do From 5ff3783d018476bad954881e39f714fac0630436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 22 Nov 2022 23:34:23 +0100 Subject: [PATCH 0402/1008] Use correct domain for fqn and InstanceView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/user.ex | 2 +- lib/pleroma/web/mastodon_api/views/instance_view.ex | 2 +- lib/pleroma/web/web_finger.ex | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ce125d608..a0d4aca66 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2252,7 +2252,7 @@ defmodule Pleroma.User do if String.contains?(user.nickname, "@") do user.nickname else - %{host: host} = URI.parse(user.ap_id) + host = Pleroma.Web.WebFinger.domain() user.nickname <> "@" <> host end end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 1b01d7371..aef02a418 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do instance = Config.get(:instance) %{ - uri: Pleroma.Web.Endpoint.url(), + uri: Pleroma.Web.WebFinger.domain(), title: Keyword.get(instance, :name), description: Keyword.get(instance, :description), short_description: Keyword.get(instance, :short_description), diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index f95dc2458..b28fad8d1 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -96,7 +96,7 @@ defmodule Pleroma.Web.WebFinger do |> XmlBuilder.to_doc() end - defp domain do + def domain do Pleroma.Config.get([__MODULE__, :domain]) || Pleroma.Web.Endpoint.host() end From c03852fbc78f8b02aef2189409f393a339ac113f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 23 Nov 2022 00:13:06 +0100 Subject: [PATCH 0403/1008] update tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- test/pleroma/user_test.exs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 7f60b959a..a8bd509a9 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -2678,13 +2678,25 @@ defmodule Pleroma.UserTest do end describe "full_nickname/1" do - test "returns fully qualified nickname for local and remote users" do - local_user = - insert(:user, nickname: "local_user", ap_id: "https://somehost.com/users/local_user") + test "returns fully qualified nickname for local users" do + local_user = insert(:user, nickname: "local_user") + assert User.full_nickname(local_user) == "local_user@localhost" + end + + test "returns fully qualified nickname for local users when using different domain for webfinger" do + clear_config([Pleroma.Web.WebFinger, :domain], "plemora.dev") + + host = Pleroma.Web.Endpoint.host() + + local_user = insert(:user, nickname: "local_user") + + assert User.full_nickname(local_user) == "local_user@plemora.dev" + end + + test "returns fully qualified nickname for remote users" do remote_user = insert(:user, nickname: "remote@host.com", local: false) - assert User.full_nickname(local_user) == "local_user@somehost.com" assert User.full_nickname(remote_user) == "remote@host.com" end From a2a69709b51692be307940c79d0befdd3c9678bb Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 24 Oct 2023 19:57:31 -0400 Subject: [PATCH 0404/1008] Bump version to 2.6.0 --- CHANGELOG.md | 38 +++++++++++++++++-- changelog.d/2023-06-deps-update.skip | 0 changelog.d/3126.fix | 1 - changelog.d/3739.skip | 0 changelog.d/3801.fix | 1 - changelog.d/3831.skip | 0 changelog.d/3848.add | 1 - changelog.d/3870.skip | 0 changelog.d/3872.remove | 1 - changelog.d/3873.fix | 1 - changelog.d/3874.remove | 1 - changelog.d/3876.skip | 0 changelog.d/3877.skip | 0 changelog.d/3878.skip | 0 changelog.d/3879.fix | 1 - changelog.d/3880.remove | 1 - changelog.d/3882.add | 1 - changelog.d/3883.fix | 1 - changelog.d/3884.fix | 1 - changelog.d/3885.fix | 1 - changelog.d/3888.fix | 1 - changelog.d/3891.fix | 1 - changelog.d/3893.skip | 0 changelog.d/3897.add | 1 - changelog.d/3899.skip | 0 changelog.d/3901.security | 1 - changelog.d/3902.skip | 0 changelog.d/3909.skip | 0 .../akkoma-xml-remote-entities.security | 1 - changelog.d/amd64-runner.skip | 0 changelog.d/attachment-type-check.fix | 1 - changelog.d/changelog-improve.skip | 0 .../check-attachment-attribution.security | 1 - changelog.d/delete-status-of-banned-user.fix | 1 - changelog.d/deprecate-scrobbles.remove | 1 - .../disable-xml-entity-resolution.security | 1 - changelog.d/distro-docs-elixir-1.11.skip | 0 changelog.d/dockerfile-config-perms.fix | 1 - changelog.d/emoji-pack-sanitization.security | 1 - changelog.d/emoji-policy.add | 1 - ...d-collection-shouldnt-break-user-fetch.fix | 1 - changelog.d/fix-object-test.fix | 1 - changelog.d/gentoo_otp.skip | 0 changelog.d/gentoo_otp_hotfix.skip | 0 changelog.d/gentoo_otp_intro.skip | 0 .../handle-report-from-deactivated-user.fix | 1 - changelog.d/lint.skip | 0 changelog.d/media-altdomain.skip | 0 changelog.d/no_new_privs.add | 1 - changelog.d/otp_perms.security | 1 - changelog.d/pipeline-triggers.skip | 0 ...revent-bypassing-authorized-fetch-mode.fix | 1 - changelog.d/punycode-mention.fix | 1 - changelog.d/quote.add | 1 - changelog.d/testfix-system-config-use.skip | 0 changelog.d/unified-streaming.add | 1 - .../update-credentials-limit-error.fix | 1 - mix.exs | 2 +- 58 files changed, 35 insertions(+), 40 deletions(-) delete mode 100644 changelog.d/2023-06-deps-update.skip delete mode 100644 changelog.d/3126.fix delete mode 100644 changelog.d/3739.skip delete mode 100644 changelog.d/3801.fix delete mode 100644 changelog.d/3831.skip delete mode 100644 changelog.d/3848.add delete mode 100644 changelog.d/3870.skip delete mode 100644 changelog.d/3872.remove delete mode 100644 changelog.d/3873.fix delete mode 100644 changelog.d/3874.remove delete mode 100644 changelog.d/3876.skip delete mode 100644 changelog.d/3877.skip delete mode 100644 changelog.d/3878.skip delete mode 100644 changelog.d/3879.fix delete mode 100644 changelog.d/3880.remove delete mode 100644 changelog.d/3882.add delete mode 100644 changelog.d/3883.fix delete mode 100644 changelog.d/3884.fix delete mode 100644 changelog.d/3885.fix delete mode 100644 changelog.d/3888.fix delete mode 100644 changelog.d/3891.fix delete mode 100644 changelog.d/3893.skip delete mode 100644 changelog.d/3897.add delete mode 100644 changelog.d/3899.skip delete mode 100644 changelog.d/3901.security delete mode 100644 changelog.d/3902.skip delete mode 100644 changelog.d/3909.skip delete mode 100644 changelog.d/akkoma-xml-remote-entities.security delete mode 100644 changelog.d/amd64-runner.skip delete mode 100644 changelog.d/attachment-type-check.fix delete mode 100644 changelog.d/changelog-improve.skip delete mode 100644 changelog.d/check-attachment-attribution.security delete mode 100644 changelog.d/delete-status-of-banned-user.fix delete mode 100644 changelog.d/deprecate-scrobbles.remove delete mode 100644 changelog.d/disable-xml-entity-resolution.security delete mode 100644 changelog.d/distro-docs-elixir-1.11.skip delete mode 100644 changelog.d/dockerfile-config-perms.fix delete mode 100644 changelog.d/emoji-pack-sanitization.security delete mode 100644 changelog.d/emoji-policy.add delete mode 100644 changelog.d/featured-collection-shouldnt-break-user-fetch.fix delete mode 100644 changelog.d/fix-object-test.fix delete mode 100644 changelog.d/gentoo_otp.skip delete mode 100644 changelog.d/gentoo_otp_hotfix.skip delete mode 100644 changelog.d/gentoo_otp_intro.skip delete mode 100644 changelog.d/handle-report-from-deactivated-user.fix delete mode 100644 changelog.d/lint.skip delete mode 100644 changelog.d/media-altdomain.skip delete mode 100644 changelog.d/no_new_privs.add delete mode 100644 changelog.d/otp_perms.security delete mode 100644 changelog.d/pipeline-triggers.skip delete mode 100644 changelog.d/prevent-bypassing-authorized-fetch-mode.fix delete mode 100644 changelog.d/punycode-mention.fix delete mode 100644 changelog.d/quote.add delete mode 100644 changelog.d/testfix-system-config-use.skip delete mode 100644 changelog.d/unified-streaming.add delete mode 100644 changelog.d/update-credentials-limit-error.fix diff --git a/CHANGELOG.md b/CHANGELOG.md index 65acfad3e..211e611ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,19 +4,49 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## Unreleased - -### Changed +## 2.6.0 +### Security +- Preload: Make generated JSON html-safe. It already was html safe because it only consists of config data that is base64 encoded, but this will keep it safe it that ever changes. +- CommonAPI: Prevent users from accessing media of other users by creating a status with reused attachment ID +- Disable XML entity resolution completely to fix a dos vulnerability ### Added - Support for Image activities, namely from Hubzilla +- Add OAuth scope descriptions +- Allow lang attribute in status text +- OnlyMedia Upload Filter +- Implement MRF policy to reject or delist according to emojis +- (hardening) Add no_new_privs=yes to OpenRC service files +- Implement quotes +- Add unified streaming endpoint ### Fixed - - rel="me" was missing its cache +- MediaProxy responses now return a sandbox CSP header +- Filter context activities using Visibility.visible_for_user? +- UploadedMedia: Add missing disposition_type to Content-Disposition +- fix not being able to fetch flash file from remote instance +- Fix abnormal behaviour when refetching a poll +- Allow non-HTTP(s) URIs in "url" fields for compatibility with "FEP-fffd: Proxy Objects" +- Fix opengraph and twitter card meta tags +- ForceMentionsInContent: fix double mentions for Mastodon/Misskey posts +- OEmbed HTML tags are now filtered +- Restrict attachments to only uploaded files only +- Fix error 404 when deleting status of a banned user +- Fix config ownership in dockerfile to pass restriction test +- Fix user fetch completely broken if featured collection is not in a supported form +- Correctly handle the situation when a poll has both "anyOf" and "oneOf" but one of them being empty +- Fix handling report from a deactivated user +- Prevent using the .json format to bypass authorized fetch mode +- Fix mentioning punycode domains when using Markdown +- Show more informative errors when profile exceeds char limits ### Removed - BREAKING: Support for passwords generated with `crypt(3)` (Gnu Social migration artifact) +- remove BBS/SSH feature, replaced by an external bridge. +- Remove a few unused indexes. +- Cleanup OStatus-era user upgrades and ap_enabled indicator +- Deprecate Pleroma's audio scrobbling ## 2.5.4 diff --git a/changelog.d/2023-06-deps-update.skip b/changelog.d/2023-06-deps-update.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3126.fix b/changelog.d/3126.fix deleted file mode 100644 index 91d396c89..000000000 --- a/changelog.d/3126.fix +++ /dev/null @@ -1 +0,0 @@ -MediaProxy responses now return a sandbox CSP header diff --git a/changelog.d/3739.skip b/changelog.d/3739.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3801.fix b/changelog.d/3801.fix deleted file mode 100644 index 8c2ec0199..000000000 --- a/changelog.d/3801.fix +++ /dev/null @@ -1 +0,0 @@ -Filter context activities using Visibility.visible_for_user? diff --git a/changelog.d/3831.skip b/changelog.d/3831.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3848.add b/changelog.d/3848.add deleted file mode 100644 index d7b1b0a84..000000000 --- a/changelog.d/3848.add +++ /dev/null @@ -1 +0,0 @@ -Add OAuth scope descriptions diff --git a/changelog.d/3870.skip b/changelog.d/3870.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3872.remove b/changelog.d/3872.remove deleted file mode 100644 index 54cbb660e..000000000 --- a/changelog.d/3872.remove +++ /dev/null @@ -1 +0,0 @@ -remove BBS/SSH feature, replaced by an external bridge. \ No newline at end of file diff --git a/changelog.d/3873.fix b/changelog.d/3873.fix deleted file mode 100644 index 4699f7b58..000000000 --- a/changelog.d/3873.fix +++ /dev/null @@ -1 +0,0 @@ -UploadedMedia: Add missing disposition_type to Content-Disposition \ No newline at end of file diff --git a/changelog.d/3874.remove b/changelog.d/3874.remove deleted file mode 100644 index a81f744bf..000000000 --- a/changelog.d/3874.remove +++ /dev/null @@ -1 +0,0 @@ -Remove a few unused indexes. diff --git a/changelog.d/3876.skip b/changelog.d/3876.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3877.skip b/changelog.d/3877.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3878.skip b/changelog.d/3878.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3879.fix b/changelog.d/3879.fix deleted file mode 100644 index 7c58cc3c2..000000000 --- a/changelog.d/3879.fix +++ /dev/null @@ -1 +0,0 @@ -fix not being able to fetch flash file from remote instance \ No newline at end of file diff --git a/changelog.d/3880.remove b/changelog.d/3880.remove deleted file mode 100644 index 113c76c85..000000000 --- a/changelog.d/3880.remove +++ /dev/null @@ -1 +0,0 @@ -Cleanup OStatus-era user upgrades and ap_enabled indicator \ No newline at end of file diff --git a/changelog.d/3882.add b/changelog.d/3882.add deleted file mode 100644 index 4712de1dc..000000000 --- a/changelog.d/3882.add +++ /dev/null @@ -1 +0,0 @@ -Allow lang attribute in status text diff --git a/changelog.d/3883.fix b/changelog.d/3883.fix deleted file mode 100644 index 6824f2013..000000000 --- a/changelog.d/3883.fix +++ /dev/null @@ -1 +0,0 @@ -Fix abnormal behaviour when refetching a poll diff --git a/changelog.d/3884.fix b/changelog.d/3884.fix deleted file mode 100644 index f8dbb2bbf..000000000 --- a/changelog.d/3884.fix +++ /dev/null @@ -1 +0,0 @@ -Allow non-HTTP(s) URIs in "url" fields for compatibility with "FEP-fffd: Proxy Objects" \ No newline at end of file diff --git a/changelog.d/3885.fix b/changelog.d/3885.fix deleted file mode 100644 index c5fbb0ed4..000000000 --- a/changelog.d/3885.fix +++ /dev/null @@ -1 +0,0 @@ -Fix opengraph and twitter card meta tags diff --git a/changelog.d/3888.fix b/changelog.d/3888.fix deleted file mode 100644 index 886aa7b39..000000000 --- a/changelog.d/3888.fix +++ /dev/null @@ -1 +0,0 @@ -ForceMentionsInContent: fix double mentions for Mastodon/Misskey posts \ No newline at end of file diff --git a/changelog.d/3891.fix b/changelog.d/3891.fix deleted file mode 100644 index f1fb62d82..000000000 --- a/changelog.d/3891.fix +++ /dev/null @@ -1 +0,0 @@ -OEmbed HTML tags are now filtered diff --git a/changelog.d/3893.skip b/changelog.d/3893.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3897.add b/changelog.d/3897.add deleted file mode 100644 index 5c4402f45..000000000 --- a/changelog.d/3897.add +++ /dev/null @@ -1 +0,0 @@ -OnlyMedia Upload Filter diff --git a/changelog.d/3899.skip b/changelog.d/3899.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3901.security b/changelog.d/3901.security deleted file mode 100644 index a3d8bd01f..000000000 --- a/changelog.d/3901.security +++ /dev/null @@ -1 +0,0 @@ -Preload: Make generated JSON html-safe. It already was html safe because it only consists of config data that is base64 encoded, but this will keep it safe it that ever changes. diff --git a/changelog.d/3902.skip b/changelog.d/3902.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/3909.skip b/changelog.d/3909.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/akkoma-xml-remote-entities.security b/changelog.d/akkoma-xml-remote-entities.security deleted file mode 100644 index 5e6725e5b..000000000 --- a/changelog.d/akkoma-xml-remote-entities.security +++ /dev/null @@ -1 +0,0 @@ -Fix XML External Entity (XXE) loading vulnerability allowing to fetch arbitary files from the server's filesystem diff --git a/changelog.d/amd64-runner.skip b/changelog.d/amd64-runner.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/attachment-type-check.fix b/changelog.d/attachment-type-check.fix deleted file mode 100644 index 9e14b75f1..000000000 --- a/changelog.d/attachment-type-check.fix +++ /dev/null @@ -1 +0,0 @@ -Restrict attachments to only uploaded files only diff --git a/changelog.d/changelog-improve.skip b/changelog.d/changelog-improve.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/check-attachment-attribution.security b/changelog.d/check-attachment-attribution.security deleted file mode 100644 index e0e46525b..000000000 --- a/changelog.d/check-attachment-attribution.security +++ /dev/null @@ -1 +0,0 @@ -CommonAPI: Prevent users from accessing media of other users by creating a status with reused attachment ID diff --git a/changelog.d/delete-status-of-banned-user.fix b/changelog.d/delete-status-of-banned-user.fix deleted file mode 100644 index 1fa6a29d8..000000000 --- a/changelog.d/delete-status-of-banned-user.fix +++ /dev/null @@ -1 +0,0 @@ -Fix error 404 when deleting status of a banned user diff --git a/changelog.d/deprecate-scrobbles.remove b/changelog.d/deprecate-scrobbles.remove deleted file mode 100644 index c453a9784..000000000 --- a/changelog.d/deprecate-scrobbles.remove +++ /dev/null @@ -1 +0,0 @@ -Deprecate Pleroma's audio scrobbling diff --git a/changelog.d/disable-xml-entity-resolution.security b/changelog.d/disable-xml-entity-resolution.security deleted file mode 100644 index db8e12f67..000000000 --- a/changelog.d/disable-xml-entity-resolution.security +++ /dev/null @@ -1 +0,0 @@ -Disable XML entity resolution completely to fix a dos vulnerability diff --git a/changelog.d/distro-docs-elixir-1.11.skip b/changelog.d/distro-docs-elixir-1.11.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/dockerfile-config-perms.fix b/changelog.d/dockerfile-config-perms.fix deleted file mode 100644 index 49ea5becb..000000000 --- a/changelog.d/dockerfile-config-perms.fix +++ /dev/null @@ -1 +0,0 @@ -- Fix config ownership in dockerfile to pass restriction test diff --git a/changelog.d/emoji-pack-sanitization.security b/changelog.d/emoji-pack-sanitization.security deleted file mode 100644 index f3218abd4..000000000 --- a/changelog.d/emoji-pack-sanitization.security +++ /dev/null @@ -1 +0,0 @@ -Emoji pack loader sanitizes pack names diff --git a/changelog.d/emoji-policy.add b/changelog.d/emoji-policy.add deleted file mode 100644 index 45510c4f6..000000000 --- a/changelog.d/emoji-policy.add +++ /dev/null @@ -1 +0,0 @@ -Implement MRF policy to reject or delist according to emojis diff --git a/changelog.d/featured-collection-shouldnt-break-user-fetch.fix b/changelog.d/featured-collection-shouldnt-break-user-fetch.fix deleted file mode 100644 index e8ce288cc..000000000 --- a/changelog.d/featured-collection-shouldnt-break-user-fetch.fix +++ /dev/null @@ -1 +0,0 @@ -Fix user fetch completely broken if featured collection is not in a supported form diff --git a/changelog.d/fix-object-test.fix b/changelog.d/fix-object-test.fix deleted file mode 100644 index 5eea719f0..000000000 --- a/changelog.d/fix-object-test.fix +++ /dev/null @@ -1 +0,0 @@ -Correctly handle the situation when a poll has both "anyOf" and "oneOf" but one of them being empty diff --git a/changelog.d/gentoo_otp.skip b/changelog.d/gentoo_otp.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/gentoo_otp_hotfix.skip b/changelog.d/gentoo_otp_hotfix.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/gentoo_otp_intro.skip b/changelog.d/gentoo_otp_intro.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/handle-report-from-deactivated-user.fix b/changelog.d/handle-report-from-deactivated-user.fix deleted file mode 100644 index 6692d1aa8..000000000 --- a/changelog.d/handle-report-from-deactivated-user.fix +++ /dev/null @@ -1 +0,0 @@ -Fix handling report from a deactivated user diff --git a/changelog.d/lint.skip b/changelog.d/lint.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/media-altdomain.skip b/changelog.d/media-altdomain.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/no_new_privs.add b/changelog.d/no_new_privs.add deleted file mode 100644 index b67396a4b..000000000 --- a/changelog.d/no_new_privs.add +++ /dev/null @@ -1 +0,0 @@ -(hardening) Add no_new_privs=yes to OpenRC service files diff --git a/changelog.d/otp_perms.security b/changelog.d/otp_perms.security deleted file mode 100644 index a3da1c677..000000000 --- a/changelog.d/otp_perms.security +++ /dev/null @@ -1 +0,0 @@ -- Reduced permissions of config files and directories, distros requiring greater permissions like group-read need to pre-create the directories \ No newline at end of file diff --git a/changelog.d/pipeline-triggers.skip b/changelog.d/pipeline-triggers.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/prevent-bypassing-authorized-fetch-mode.fix b/changelog.d/prevent-bypassing-authorized-fetch-mode.fix deleted file mode 100644 index 12f7260d7..000000000 --- a/changelog.d/prevent-bypassing-authorized-fetch-mode.fix +++ /dev/null @@ -1 +0,0 @@ -Prevent using the .json format to bypass authorized fetch mode \ No newline at end of file diff --git a/changelog.d/punycode-mention.fix b/changelog.d/punycode-mention.fix deleted file mode 100644 index f013c2dac..000000000 --- a/changelog.d/punycode-mention.fix +++ /dev/null @@ -1 +0,0 @@ -Fix mentioning punycode domains when using Markdown diff --git a/changelog.d/quote.add b/changelog.d/quote.add deleted file mode 100644 index 1c368ae75..000000000 --- a/changelog.d/quote.add +++ /dev/null @@ -1 +0,0 @@ -Implement quotes diff --git a/changelog.d/testfix-system-config-use.skip b/changelog.d/testfix-system-config-use.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/unified-streaming.add b/changelog.d/unified-streaming.add deleted file mode 100644 index 84821fcc8..000000000 --- a/changelog.d/unified-streaming.add +++ /dev/null @@ -1 +0,0 @@ -Add unified streaming endpoint diff --git a/changelog.d/update-credentials-limit-error.fix b/changelog.d/update-credentials-limit-error.fix deleted file mode 100644 index 7682f958e..000000000 --- a/changelog.d/update-credentials-limit-error.fix +++ /dev/null @@ -1 +0,0 @@ -Show more informative errors when profile exceeds char limits diff --git a/mix.exs b/mix.exs index b071e7c7b..082b39e55 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.5.54"), + version: version("2.6.0"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix] ++ Mix.compilers(), From 6a13c2d180177692b976b179f0bd86f8f93e2803 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 25 Oct 2023 20:44:47 -0400 Subject: [PATCH 0405/1008] Add collect-changelog script --- .gitlab/merge_request_templates/Release.md | 2 +- tools/collect-changelog | 27 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100755 tools/collect-changelog diff --git a/.gitlab/merge_request_templates/Release.md b/.gitlab/merge_request_templates/Release.md index 9638d6d11..e57556e6c 100644 --- a/.gitlab/merge_request_templates/Release.md +++ b/.gitlab/merge_request_templates/Release.md @@ -1,6 +1,6 @@ ### Release checklist * [ ] Bump version in `mix.exs` -* [ ] Compile a changelog +* [ ] Compile a changelog with the `tools/collect-changelog` script * [ ] Create an MR with an announcement to pleroma.social #### post-merge * [ ] Tag the release on the merge commit diff --git a/tools/collect-changelog b/tools/collect-changelog new file mode 100755 index 000000000..1e12d5640 --- /dev/null +++ b/tools/collect-changelog @@ -0,0 +1,27 @@ +#!/bin/sh + +collectType() { + local suffix="$1" + local header="$2" + local printed=0 + for file in changelog.d/*."$suffix"; do + if [ '!' -f "$file" ]; then + continue + fi + if [ "$printed" = 0 ]; then + echo + echo "### $header" + printed=1 + fi + # Normalize any trailing newlines/spaces, etc. + echo "- $(cat "$file")" + done +} + +collectType security Security +collectType change Changed +collectType add Added +collectType fix Fixed +collectType remove Removed + +rm changelog.d/* From 6b8c5e12dfe759ac1286e81e72ad7f8727e01386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 26 Oct 2023 23:30:38 +0200 Subject: [PATCH 0406/1008] Add contact account to InstanceView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/instance-contact-account.add | 1 + config/description.exs | 6 ++++++ .../web/mastodon_api/views/instance_view.ex | 17 +++++++++++++++++ .../controllers/instance_controller_test.exs | 12 ++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 changelog.d/instance-contact-account.add diff --git a/changelog.d/instance-contact-account.add b/changelog.d/instance-contact-account.add new file mode 100644 index 000000000..e119446d2 --- /dev/null +++ b/changelog.d/instance-contact-account.add @@ -0,0 +1 @@ +Add contact account to InstanceView \ No newline at end of file diff --git a/config/description.exs b/config/description.exs index d18649ae8..239ba7cb0 100644 --- a/config/description.exs +++ b/config/description.exs @@ -566,6 +566,12 @@ config :pleroma, :config_description, [ "Cool instance" ] }, + %{ + key: :contact_username, + type: :string, + description: "Instance owner username", + suggestions: ["admin"] + }, %{ key: :limit, type: :integer, diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 1b01d7371..06df63b0f 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -30,6 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do languages: Keyword.get(instance, :languages, ["en"]), registrations: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), + contact_account: contact_account(Keyword.get(instance, :contact_username)), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), max_media_attachments: Keyword.get(instance, :max_media_attachments), @@ -141,4 +142,20 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end + + defp contact_account(nil), do: nil + + defp contact_account("@" <> username) do + contact_account(username) + end + + defp contact_account(username) do + user = Pleroma.User.get_cached_by_nickname(username) + + if user do + Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil}) + else + nil + end + end end diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index a556ef6a8..cfa86468f 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -106,4 +106,16 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do |> get("/api/v1/instance") |> json_response_and_validate_schema(200) end + + test "get instance contact information", %{conn: conn} do + user = insert(:user, %{local: true}) + + clear_config([:instance, :contact_username], user.nickname) + + conn = get(conn, "/api/v1/instance") + + assert result = json_response_and_validate_schema(conn, 200) + + assert result["contact_account"]["id"] == user.id + end end From c6cedbb8106a16527e48ac8ae03907e1d66c5a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 28 Oct 2023 00:07:18 +0200 Subject: [PATCH 0407/1008] InstanceV2: skip auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/mastodon_api/controllers/instance_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 3757a850a..3e664903a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_auth when action in [:show, :peers]) + plug(:skip_auth when action in [:show, :show2, :peers]) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.InstanceOperation From b6a9d87f16a4806eab7a6da874d6f75b65d4f214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 15 Mar 2023 19:44:42 +0100 Subject: [PATCH 0408/1008] Display reposted replies with exclude_replies: true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/show-reposter-replies.add | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changelog.d/show-reposter-replies.add diff --git a/changelog.d/show-reposter-replies.add b/changelog.d/show-reposter-replies.add new file mode 100644 index 000000000..3b852ec3b --- /dev/null +++ b/changelog.d/show-reposter-replies.add @@ -0,0 +1 @@ +Display reposted replies with exclude_replies: true \ No newline at end of file diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 3979d418e..4b956c680 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -964,8 +964,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_replies(query, %{exclude_replies: true}) do from( - [_activity, object] in query, - where: fragment("?->>'inReplyTo' is null", object.data) + [activity, object] in query, + where: + fragment("?->>'inReplyTo' is null or ?->>'type' = 'Announce'", object.data, activity.data) ) end From 93370b870a8d16b5dada5ef7c7e3ef2de378395e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 7 Aug 2022 23:56:52 +0200 Subject: [PATCH 0409/1008] Expose nonAnonymous field from Smithereen polls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../object_validators/question_validator.ex | 1 + lib/pleroma/web/api_spec/schemas/poll.ex | 11 ++++++++++- lib/pleroma/web/mastodon_api/views/poll_view.ex | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 621085e6c..7f9d4d648 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do field(:closed, ObjectValidators.DateTime) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) + field(:nonAnonymous, :boolean) embeds_many(:anyOf, QuestionOptionsValidator) embeds_many(:oneOf, QuestionOptionsValidator) end diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 91570582b..cb2ffdc68 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -56,6 +56,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do } }, description: "Possible answers for the poll." + }, + pleroma: %Schema{ + type: :object, + properties: %{ + non_anonymous: %Schema{type: :boolean, description: "Is the voters collection public?"} + } } }, example: %{ @@ -79,7 +85,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do votes_count: 4 } ], - emojis: [] + emojis: [], + pleroma: %{ + non_anonymous: false + } } }) end diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 34e23873e..1e3c9f36d 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -21,7 +21,10 @@ defmodule Pleroma.Web.MastodonAPI.PollView do votes_count: votes_count, voters_count: voters_count(object), options: options, - emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) + emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]), + pleroma: %{ + non_anonymous: object.data["nonAnonymous"] || false + } } if params[:for] do From e5bd1ee80154b31457567c3d31de6c63e0146352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 8 Aug 2022 10:45:43 +0200 Subject: [PATCH 0410/1008] Add entry to @context, tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/public-polls.add | 1 + priv/static/schemas/litepub-0.1.jsonld | 4 +++- .../smithereen_non_anonymous_poll.json | 1 + test/fixtures/tesla_mock/smithereen_user.json | 1 + .../web/mastodon_api/views/poll_view_test.exs | 10 +++++++++- test/support/http_request_mock.ex | 18 ++++++++++++++++++ 6 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 changelog.d/public-polls.add create mode 100644 test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json create mode 100644 test/fixtures/tesla_mock/smithereen_user.json diff --git a/changelog.d/public-polls.add b/changelog.d/public-polls.add new file mode 100644 index 000000000..0dae0c38e --- /dev/null +++ b/changelog.d/public-polls.add @@ -0,0 +1 @@ +Expose nonAnonymous field from Smithereen polls \ No newline at end of file diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index b499a96f5..d156cba55 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -40,7 +40,9 @@ "@type": "@id" }, "vcard": "http://www.w3.org/2006/vcard/ns#", - "formerRepresentations": "litepub:formerRepresentations" + "formerRepresentations": "litepub:formerRepresentations", + "sm": "http://smithereen.software/ns#", + "nonAnonymous": "sm:nonAnonymous" } ] } diff --git a/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json b/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json new file mode 100644 index 000000000..2b343ea64 --- /dev/null +++ b/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json @@ -0,0 +1 @@ +{"type":"Question","id":"https://friends.grishka.me/posts/54642","attributedTo":"https://friends.grishka.me/users/1","content":"

здесь тоже можно что-то написать отдельно от опроса

","published":"2021-09-04T00:22:16Z","url":"https://friends.grishka.me/posts/54642","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://friends.grishka.me/users/1/followers"],"replies":{"type":"Collection","id":"https://friends.grishka.me/posts/54642/replies","first":{"type":"CollectionPage","items":[],"partOf":"https://friends.grishka.me/posts/54642/replies","next":"https://friends.grishka.me/posts/54642/replies?page=1"}},"sensitive":false,"likes":"https://friends.grishka.me/posts/54642/likes","name":"тестовый опрос","oneOf":[{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/76","name":"тестовый ответ 1","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/76/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/77","name":"тестовый ответ 2","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/77/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/78","name":"тестовый ответ 3","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/78/votes","totalItems":6,"items":[]}}],"votersCount":14,"nonAnonymous":true,"@context":["https://www.w3.org/ns/activitystreams",{"sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#","sm":"http://smithereen.software/ns#","votersCount":"toot:votersCount","nonAnonymous":"sm:nonAnonymous"}]} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/smithereen_user.json b/test/fixtures/tesla_mock/smithereen_user.json new file mode 100644 index 000000000..6468fc519 --- /dev/null +++ b/test/fixtures/tesla_mock/smithereen_user.json @@ -0,0 +1 @@ +{"type":"Person","id":"https://friends.grishka.me/users/1","name":"Григорий Клюшников","icon":{"type":"Image","image":{"type":"Image","url":"https://friends.grishka.me/i/6QLsOws97AWp5N_osd74C1IC1ijnFopyCBD9MSEeXNQ/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg","width":1280,"height":960},"width":573,"height":572,"cropRegion":[0.26422762870788574,0.3766937553882599,0.7113820910453796,0.9728997349739075],"url":"https://friends.grishka.me/i/ql_49PQcETAWgY_nC-Qj63H_Oa6FyOAEoWFkUSSkUvQ/c:573:572:nowe:338:362/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg"},"summary":"

Делаю эту хрень, пытаюсь вырвать социальные сети из жадных лап корпораций

\n

\n

\n

\n

\n

\n

\n

\n

This server does NOT support direct messages. Please write me on Telegram or Matrix.

","url":"https://friends.grishka.me/grishka","preferredUsername":"grishka","inbox":"https://friends.grishka.me/users/1/inbox","outbox":"https://friends.grishka.me/users/1/outbox","followers":"https://friends.grishka.me/users/1/followers","following":"https://friends.grishka.me/users/1/following","endpoints":{"sharedInbox":"https://friends.grishka.me/activitypub/sharedInbox","collectionSimpleQuery":"https://friends.grishka.me/users/1/collectionQuery"},"publicKey":{"id":"https://friends.grishka.me/users/1#main-key","owner":"https://friends.grishka.me/users/1","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjlakm+i/d9ER/hIeR7KfiFW+SdLZj2SkKIeM8cmR+YFJuh9ghFqXrkFEjcaqUnAFqe5gYDNSQACnDLA8y4DnzjfGNIohKAnRoa9x6GORmfKQvcnjaTZ53S1NvUiPPyc0Pv/vfCtY/Ab0CEXe5BLqL38oZn817Jf7pBrPRTYH7m012kvwAUTT6k0Y8lPITBEG7nzYbbuGcrN9Y/RDdwE08jmBXlZ45bahRH3VNXVpQE17dCzJB+7k+iJ1R7YCoI+DuMlBYGXGE2KVk46NZTuLnOjFV9SyXfWX4/SrJM4oxev+SX2N75tQgmNZmVVHeqg2ZcbC0WCfNjJOi2HHS9MujwIDAQAB\n-----END PUBLIC KEY-----\n"},"wall":"https://friends.grishka.me/users/1/wall","firstName":"Григорий","lastName":"Клюшников","middleName":"Александрович","vcard:bday":"1993-01-22","gender":"http://schema.org#Male","supportsFriendRequests":true,"friends":"https://friends.grishka.me/users/1/friends","groups":"https://friends.grishka.me/users/1/groups","capabilities":{"supportsFriendRequests":true},"@context":["https://www.w3.org/ns/activitystreams",{"sm":"http://smithereen.software/ns#","cropRegion":{"@id":"sm:cropRegion","@container":"@list"},"wall":{"@id":"sm:wall","@type":"@id"},"collectionSimpleQuery":"sm:collectionSimpleQuery","sc":"http://schema.org#","firstName":"sc:givenName","lastName":"sc:familyName","middleName":"sc:additionalName","gender":{"@id":"sc:gender","@type":"sc:GenderType"},"maidenName":"sm:maidenName","friends":{"@id":"sm:friends","@type":"@id"},"groups":{"@id":"sm:groups","@type":"@id"},"vcard":"http://www.w3.org/2006/vcard/ns#","capabilities":"litepub:capabilities","supportsFriendRequests":"sm:supportsFriendRequests","litepub":"http://litepub.social/ns#"},"https://w3id.org/security/v1"]} \ No newline at end of file diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs index a73d862fd..3aa73c224 100644 --- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -43,7 +43,8 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do %{title: "why are you even asking?", votes_count: 0} ], votes_count: 0, - voters_count: 0 + voters_count: 0, + pleroma: %{non_anonymous: false} } result = PollView.render("show.json", %{object: object}) @@ -165,4 +166,11 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do ] } = PollView.render("show.json", %{object: object}) end + + test "that poll is non anonymous" do + object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true) + result = PollView.render("show.json", %{object: object}) + + assert result[:pleroma][:non_anonymous] == true + end end diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 78a367024..2707804bc 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1464,6 +1464,24 @@ defmodule HttpRequestMock do }} end + def get("https://friends.grishka.me/posts/54642", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json"), + headers: activitypub_object_headers() + }} + end + + def get("https://friends.grishka.me/users/1", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/smithereen_user.json"), + headers: activitypub_object_headers() + }} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} From ab894d98f4be5aadc033b71dacc499b85b579bc1 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 29 Oct 2023 12:59:03 -0400 Subject: [PATCH 0411/1008] Bundle 2.6.0 frontend --- priv/static/index.html | 2 +- priv/static/static/css/5948.06d2a0d84620cba6a4fb.css | 2 -- .../static/css/5948.06d2a0d84620cba6a4fb.css.map | 1 - priv/static/static/css/7586.0d43f70bc6240422f179.css | 2 ++ .../static/css/7586.0d43f70bc6240422f179.css.map | 1 + priv/static/static/css/7962.76663e78ad5ea0bb0b90.css | 11 +++++++++++ .../static/css/7962.76663e78ad5ea0bb0b90.css.map | 1 + priv/static/static/css/9114.8def3b2b7fe70b3b3712.css | 11 ----------- .../static/css/9114.8def3b2b7fe70b3b3712.css.map | 1 - priv/static/static/css/9801.cfe503d4c949ae0c3813.css | 2 ++ .../static/css/9801.cfe503d4c949ae0c3813.css.map | 1 + priv/static/static/css/app.48e52505beba5b9ab69b.css | 2 -- .../static/css/app.48e52505beba5b9ab69b.css.map | 1 - priv/static/static/css/app.c18a2c80794a1b699a61.css | 2 ++ .../static/css/app.c18a2c80794a1b699a61.css.map | 1 + priv/static/static/js/159.3a9274574f1e33801c4a.js.map | 1 - ...4574f1e33801c4a.js => 159.903e90c9de8ef6c67077.js} | 2 +- priv/static/static/js/159.903e90c9de8ef6c67077.js.map | 1 + priv/static/static/js/2724.e4840c73281069ba54ab.js | 3 --- .../static/static/js/2724.e4840c73281069ba54ab.js.map | 1 - priv/static/static/js/3733.7060d1e6bca813125a0c.js | 3 +++ ...E.txt => 3733.7060d1e6bca813125a0c.js.LICENSE.txt} | 0 .../static/static/js/3733.7060d1e6bca813125a0c.js.map | 1 + ...79b200a6c89c4958.js => 48.b5ecdbc517423af07ca4.js} | 6 +++--- ...NSE.txt => 48.b5ecdbc517423af07ca4.js.LICENSE.txt} | 4 ++-- priv/static/static/js/48.b5ecdbc517423af07ca4.js.map | 1 + priv/static/static/js/48.d7e479b200a6c89c4958.js.map | 1 - priv/static/static/js/5948.2b7b4e97487f2539eb44.js | 2 -- .../static/static/js/5948.2b7b4e97487f2539eb44.js.map | 1 - ...a80a7373e4e5f8.js => 6464.eb9c90a1c948cde554e9.js} | 2 +- .../static/static/js/6464.eb9c90a1c948cde554e9.js.map | 1 + .../static/static/js/6464.fea96fa80a7373e4e5f8.js.map | 1 - priv/static/static/js/7586.981b2305a0019f6042a5.js | 2 ++ .../static/static/js/7586.981b2305a0019f6042a5.js.map | 1 + priv/static/static/js/7962.e25d40b042f8ee7389c3.js | 2 ++ .../static/static/js/7962.e25d40b042f8ee7389c3.js.map | 1 + priv/static/static/js/9060.24271e167e0471a1a732.js | 2 ++ .../static/static/js/9060.24271e167e0471a1a732.js.map | 1 + priv/static/static/js/9114.e761a1c6846fea99aaf1.js | 2 -- .../static/static/js/9114.e761a1c6846fea99aaf1.js.map | 1 - priv/static/static/js/9801.99ace6b5dc657bf1a65b.js | 2 ++ .../static/static/js/9801.99ace6b5dc657bf1a65b.js.map | 1 + priv/static/static/js/app.7c4b412b26221a7c8572.js | 2 ++ priv/static/static/js/app.7c4b412b26221a7c8572.js.map | 1 + priv/static/static/js/app.8d2126d35dba9482db51.js | 2 -- priv/static/static/js/app.8d2126d35dba9482db51.js.map | 1 - .../static/js/i18n/ar-json.4916f840147303aa65fe.js | 2 ++ .../js/i18n/ar-json.4916f840147303aa65fe.js.map | 1 + .../static/js/i18n/ar-json.d09609af3224232857d6.js | 2 -- .../js/i18n/ar-json.d09609af3224232857d6.js.map | 1 - .../static/js/i18n/eo-json.6c62eef99e850912498b.js | 2 ++ .../js/i18n/eo-json.6c62eef99e850912498b.js.map | 1 + .../static/js/i18n/eo-json.d81690d5be30b23e516b.js | 2 -- .../js/i18n/eo-json.d81690d5be30b23e516b.js.map | 1 - .../static/js/i18n/id-json.3e42564ce7a3a847ecb0.js | 2 -- .../js/i18n/id-json.3e42564ce7a3a847ecb0.js.map | 1 - .../static/js/i18n/id-json.e5c9ee768155f88128b9.js | 2 ++ .../js/i18n/id-json.e5c9ee768155f88128b9.js.map | 1 + .../static/js/i18n/ko-json.4bd28b26a7390a09afc2.js | 2 -- .../js/i18n/ko-json.4bd28b26a7390a09afc2.js.map | 1 - .../static/js/i18n/ko-json.9029d09084bb22d8b705.js | 2 ++ .../js/i18n/ko-json.9029d09084bb22d8b705.js.map | 1 + .../js/i18n/nan-TW-json.7f2789d8a461e86d1734.js | 2 ++ .../js/i18n/nan-TW-json.7f2789d8a461e86d1734.js.map | 1 + ...197374a5dac.js => zh-json.5831b903c3e6d281f122.js} | 4 ++-- .../js/i18n/zh-json.5831b903c3e6d281f122.js.map | 1 + .../js/i18n/zh-json.63e4c9fe0197374a5dac.js.map | 1 - .../js/i18n/zh_Hant-json.bfa569654a5cd74767ce.js.map | 1 - ...4767ce.js => zh_Hant-json.f7e1d0f4b873c60d6396.js} | 4 ++-- .../js/i18n/zh_Hant-json.f7e1d0f4b873c60d6396.js.map | 1 + priv/static/sw-pleroma.js | 4 ++-- priv/static/sw-pleroma.js.map | 2 +- 72 files changed, 75 insertions(+), 63 deletions(-) delete mode 100644 priv/static/static/css/5948.06d2a0d84620cba6a4fb.css delete mode 100644 priv/static/static/css/5948.06d2a0d84620cba6a4fb.css.map create mode 100644 priv/static/static/css/7586.0d43f70bc6240422f179.css create mode 100644 priv/static/static/css/7586.0d43f70bc6240422f179.css.map create mode 100644 priv/static/static/css/7962.76663e78ad5ea0bb0b90.css create mode 100644 priv/static/static/css/7962.76663e78ad5ea0bb0b90.css.map delete mode 100644 priv/static/static/css/9114.8def3b2b7fe70b3b3712.css delete mode 100644 priv/static/static/css/9114.8def3b2b7fe70b3b3712.css.map create mode 100644 priv/static/static/css/9801.cfe503d4c949ae0c3813.css create mode 100644 priv/static/static/css/9801.cfe503d4c949ae0c3813.css.map delete mode 100644 priv/static/static/css/app.48e52505beba5b9ab69b.css delete mode 100644 priv/static/static/css/app.48e52505beba5b9ab69b.css.map create mode 100644 priv/static/static/css/app.c18a2c80794a1b699a61.css create mode 100644 priv/static/static/css/app.c18a2c80794a1b699a61.css.map delete mode 100644 priv/static/static/js/159.3a9274574f1e33801c4a.js.map rename priv/static/static/js/{159.3a9274574f1e33801c4a.js => 159.903e90c9de8ef6c67077.js} (96%) create mode 100644 priv/static/static/js/159.903e90c9de8ef6c67077.js.map delete mode 100644 priv/static/static/js/2724.e4840c73281069ba54ab.js delete mode 100644 priv/static/static/js/2724.e4840c73281069ba54ab.js.map create mode 100644 priv/static/static/js/3733.7060d1e6bca813125a0c.js rename priv/static/static/js/{2724.e4840c73281069ba54ab.js.LICENSE.txt => 3733.7060d1e6bca813125a0c.js.LICENSE.txt} (100%) create mode 100644 priv/static/static/js/3733.7060d1e6bca813125a0c.js.map rename priv/static/static/js/{48.d7e479b200a6c89c4958.js => 48.b5ecdbc517423af07ca4.js} (59%) rename priv/static/static/js/{48.d7e479b200a6c89c4958.js.LICENSE.txt => 48.b5ecdbc517423af07ca4.js.LICENSE.txt} (77%) create mode 100644 priv/static/static/js/48.b5ecdbc517423af07ca4.js.map delete mode 100644 priv/static/static/js/48.d7e479b200a6c89c4958.js.map delete mode 100644 priv/static/static/js/5948.2b7b4e97487f2539eb44.js delete mode 100644 priv/static/static/js/5948.2b7b4e97487f2539eb44.js.map rename priv/static/static/js/{6464.fea96fa80a7373e4e5f8.js => 6464.eb9c90a1c948cde554e9.js} (98%) create mode 100644 priv/static/static/js/6464.eb9c90a1c948cde554e9.js.map delete mode 100644 priv/static/static/js/6464.fea96fa80a7373e4e5f8.js.map create mode 100644 priv/static/static/js/7586.981b2305a0019f6042a5.js create mode 100644 priv/static/static/js/7586.981b2305a0019f6042a5.js.map create mode 100644 priv/static/static/js/7962.e25d40b042f8ee7389c3.js create mode 100644 priv/static/static/js/7962.e25d40b042f8ee7389c3.js.map create mode 100644 priv/static/static/js/9060.24271e167e0471a1a732.js create mode 100644 priv/static/static/js/9060.24271e167e0471a1a732.js.map delete mode 100644 priv/static/static/js/9114.e761a1c6846fea99aaf1.js delete mode 100644 priv/static/static/js/9114.e761a1c6846fea99aaf1.js.map create mode 100644 priv/static/static/js/9801.99ace6b5dc657bf1a65b.js create mode 100644 priv/static/static/js/9801.99ace6b5dc657bf1a65b.js.map create mode 100644 priv/static/static/js/app.7c4b412b26221a7c8572.js create mode 100644 priv/static/static/js/app.7c4b412b26221a7c8572.js.map delete mode 100644 priv/static/static/js/app.8d2126d35dba9482db51.js delete mode 100644 priv/static/static/js/app.8d2126d35dba9482db51.js.map create mode 100644 priv/static/static/js/i18n/ar-json.4916f840147303aa65fe.js create mode 100644 priv/static/static/js/i18n/ar-json.4916f840147303aa65fe.js.map delete mode 100644 priv/static/static/js/i18n/ar-json.d09609af3224232857d6.js delete mode 100644 priv/static/static/js/i18n/ar-json.d09609af3224232857d6.js.map create mode 100644 priv/static/static/js/i18n/eo-json.6c62eef99e850912498b.js create mode 100644 priv/static/static/js/i18n/eo-json.6c62eef99e850912498b.js.map delete mode 100644 priv/static/static/js/i18n/eo-json.d81690d5be30b23e516b.js delete mode 100644 priv/static/static/js/i18n/eo-json.d81690d5be30b23e516b.js.map delete mode 100644 priv/static/static/js/i18n/id-json.3e42564ce7a3a847ecb0.js delete mode 100644 priv/static/static/js/i18n/id-json.3e42564ce7a3a847ecb0.js.map create mode 100644 priv/static/static/js/i18n/id-json.e5c9ee768155f88128b9.js create mode 100644 priv/static/static/js/i18n/id-json.e5c9ee768155f88128b9.js.map delete mode 100644 priv/static/static/js/i18n/ko-json.4bd28b26a7390a09afc2.js delete mode 100644 priv/static/static/js/i18n/ko-json.4bd28b26a7390a09afc2.js.map create mode 100644 priv/static/static/js/i18n/ko-json.9029d09084bb22d8b705.js create mode 100644 priv/static/static/js/i18n/ko-json.9029d09084bb22d8b705.js.map create mode 100644 priv/static/static/js/i18n/nan-TW-json.7f2789d8a461e86d1734.js create mode 100644 priv/static/static/js/i18n/nan-TW-json.7f2789d8a461e86d1734.js.map rename priv/static/static/js/i18n/{zh-json.63e4c9fe0197374a5dac.js => zh-json.5831b903c3e6d281f122.js} (58%) create mode 100644 priv/static/static/js/i18n/zh-json.5831b903c3e6d281f122.js.map delete mode 100644 priv/static/static/js/i18n/zh-json.63e4c9fe0197374a5dac.js.map delete mode 100644 priv/static/static/js/i18n/zh_Hant-json.bfa569654a5cd74767ce.js.map rename priv/static/static/js/i18n/{zh_Hant-json.bfa569654a5cd74767ce.js => zh_Hant-json.f7e1d0f4b873c60d6396.js} (61%) create mode 100644 priv/static/static/js/i18n/zh_Hant-json.f7e1d0f4b873c60d6396.js.map diff --git a/priv/static/index.html b/priv/static/index.html index 7dd5d0b78..52ff685c0 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/priv/static/static/css/5948.06d2a0d84620cba6a4fb.css b/priv/static/static/css/5948.06d2a0d84620cba6a4fb.css deleted file mode 100644 index b14e14143..000000000 --- a/priv/static/static/css/5948.06d2a0d84620cba6a4fb.css +++ /dev/null @@ -1,2 +0,0 @@ -.async-component-error{align-items:center;display:flex;height:100%;justify-content:center}.async-component-error .btn{margin:.5em;padding:.5em 2em}.settings-modal{overflow:hidden}.settings-modal .option-list,.settings-modal .setting-list{list-style-type:none;padding-left:2em}.settings-modal .option-list li,.settings-modal .setting-list li{margin-bottom:.5em}.settings-modal .option-list .suboptions,.settings-modal .setting-list .suboptions{margin-top:.3em}.settings-modal .settings-modal-panel{height:90vh;max-width:90vw;overflow:hidden;transition:transform;transition-duration:.3s;transition-timing-function:ease-in-out;width:1000px}@media (max-width:800px){.settings-modal .settings-modal-panel{height:100%;max-width:100vw}}.settings-modal .settings-modal-panel>.panel-body{height:100%;overflow-y:hidden}.settings-modal .settings-modal-panel>.panel-body .btn{min-height:2em;min-width:10em;padding:0 2em}.settings-modal .settings-footer{display:flex}.settings-modal .settings-footer>*{margin-right:.5em}.settings-modal .settings-footer .extra-content{display:flex;flex-grow:1}.settings-modal.peek .settings-modal-panel{transform:translateY(calc(50vh + 50% - 50px))}@media (max-width:800px){.settings-modal.peek .settings-modal-panel{transform:translateY(calc(100% - 50px))}} -/*# sourceMappingURL=5948.06d2a0d84620cba6a4fb.css.map*/ \ No newline at end of file diff --git a/priv/static/static/css/5948.06d2a0d84620cba6a4fb.css.map b/priv/static/static/css/5948.06d2a0d84620cba6a4fb.css.map deleted file mode 100644 index 72f22de67..000000000 --- a/priv/static/static/css/5948.06d2a0d84620cba6a4fb.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"static/css/5948.06d2a0d84620cba6a4fb.css","mappings":"AACA,uBAGE,mBAFA,aACA,YAEA,uBAEA,4BACE,YACA,iBCPJ,gBACE,gBAEA,2DAEE,qBACA,iBAEA,iEACE,mBAGF,mFACE,gBAIJ,sCAOE,YADA,eALA,gBACA,qBAEA,wBADA,uCAEA,YAEA,CAEA,yBATF,sCAWI,YADA,eACA,EAGF,kDACE,YACA,kBAEA,uDACE,eACA,eACA,cAKN,iCACE,aAEA,mCACE,kBAGF,gDACE,aACA,YAKF,2CASE,8CAEA,yBAXF,2CAgBI","sources":["webpack://pleroma_fe/./src/components/async_component_error/async_component_error.vue","webpack://pleroma_fe/./src/components/settings_modal/settings_modal.scss"],"sourcesContent":["\n.async-component-error {\n display: flex;\n height: 100%;\n align-items: center;\n justify-content: center;\n\n .btn {\n margin: 0.5em;\n padding: 0.5em 2em;\n }\n}\n","@import \"src/variables\";\n\n.settings-modal {\n overflow: hidden;\n\n .setting-list,\n .option-list {\n list-style-type: none;\n padding-left: 2em;\n\n li {\n margin-bottom: 0.5em;\n }\n\n .suboptions {\n margin-top: 0.3em;\n }\n }\n\n .settings-modal-panel {\n overflow: hidden;\n transition: transform;\n transition-timing-function: ease-in-out;\n transition-duration: 300ms;\n width: 1000px;\n max-width: 90vw;\n height: 90vh;\n\n @media all and (max-width: 800px) {\n max-width: 100vw;\n height: 100%;\n }\n\n >.panel-body {\n height: 100%;\n overflow-y: hidden;\n\n .btn {\n min-height: 2em;\n min-width: 10em;\n padding: 0 2em;\n }\n }\n }\n\n .settings-footer {\n display: flex;\n\n >* {\n margin-right: 0.5em;\n }\n\n .extra-content {\n display: flex;\n flex-grow: 1;\n }\n }\n\n &.peek {\n .settings-modal-panel {\n /* Explanation:\n * Modal is positioned vertically centered.\n * 100vh - 100% = Distance between modal's top+bottom boundaries and screen\n * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen\n * + 100% - we move modal completely off-screen, it's top boundary touches\n * bottom of the screen\n * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible\n */\n transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));\n\n @media all and (max-width: 800px) {\n /* For mobile, the modal takes 100% of the available screen.\n This ensures the minimized modal is always 50px above the browser bottom\n bar regardless of whether or not it is visible.\n */\n transform: translateY(calc(100% - 50px));\n }\n }\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/7586.0d43f70bc6240422f179.css b/priv/static/static/css/7586.0d43f70bc6240422f179.css new file mode 100644 index 000000000..7da2aa2ea --- /dev/null +++ b/priv/static/static/css/7586.0d43f70bc6240422f179.css @@ -0,0 +1,2 @@ +.async-component-error{align-items:center;display:flex;height:100%;justify-content:center}.async-component-error .btn{margin:.5em;padding:.5em 2em}.settings-modal{overflow:hidden}.settings-modal .option-list,.settings-modal .setting-list{list-style-type:none;padding-left:2em}.settings-modal .option-list li,.settings-modal .setting-list li{margin-bottom:.5em}.settings-modal .option-list .suboptions,.settings-modal .setting-list .suboptions{margin-top:.3em}.settings-modal .setting-description{font-size:70%;margin-bottom:2em;margin-top:.2em}.settings-modal .settings-modal-panel{height:90vh;max-width:90vw;overflow:hidden;transition:transform;transition-duration:.3s;transition-timing-function:ease-in-out;width:1000px}@media (max-width:800px){.settings-modal .settings-modal-panel{height:100%;max-width:100vw}}.settings-modal .settings-modal-panel>.panel-body{height:100%;overflow-y:hidden}.settings-modal .settings-modal-panel>.panel-body .btn{min-height:2em}.settings-modal .settings-modal-panel>.panel-body .btn:not(.dropdown-button){padding:0 2em}.settings-modal .settings-footer{display:flex;flex-wrap:wrap;line-height:2}.settings-modal .settings-footer>*{margin-right:.5em}.settings-modal .settings-footer .extra-content{display:flex;flex-grow:1}.settings-modal.peek .settings-modal-panel{transform:translateY(calc(50vh + 50% - 50px))}@media (max-width:800px){.settings-modal.peek .settings-modal-panel{transform:translateY(calc(100% - 50px))}} +/*# sourceMappingURL=7586.0d43f70bc6240422f179.css.map*/ \ No newline at end of file diff --git a/priv/static/static/css/7586.0d43f70bc6240422f179.css.map b/priv/static/static/css/7586.0d43f70bc6240422f179.css.map new file mode 100644 index 000000000..f8f61fe6e --- /dev/null +++ b/priv/static/static/css/7586.0d43f70bc6240422f179.css.map @@ -0,0 +1 @@ +{"version":3,"file":"static/css/7586.0d43f70bc6240422f179.css","mappings":"AACA,uBAGE,mBAFA,aACA,YAEA,uBAEA,4BACE,YACA,iBCPJ,gBACE,gBAEA,2DAEE,qBACA,iBAEA,iEACE,mBAGF,mFACE,gBAIJ,qCAGE,cADA,kBADA,eAEA,CAGF,sCAOE,YADA,eALA,gBACA,qBAEA,wBADA,uCAEA,YAEA,CAEA,yBATF,sCAWI,YADA,eACA,EAGF,kDACE,YACA,kBAEA,uDACE,eAGF,6EACE,cAKN,iCACE,aACA,eACA,cAEA,mCACE,kBAGF,gDACE,aACA,YAKF,2CASE,8CAEA,yBAXF,2CAgBI","sources":["webpack://pleroma_fe/./src/components/async_component_error/async_component_error.vue","webpack://pleroma_fe/./src/components/settings_modal/settings_modal.scss"],"sourcesContent":["\n.async-component-error {\n display: flex;\n height: 100%;\n align-items: center;\n justify-content: center;\n\n .btn {\n margin: 0.5em;\n padding: 0.5em 2em;\n }\n}\n","@import \"src/variables\";\n\n.settings-modal {\n overflow: hidden;\n\n .setting-list,\n .option-list {\n list-style-type: none;\n padding-left: 2em;\n\n li {\n margin-bottom: 0.5em;\n }\n\n .suboptions {\n margin-top: 0.3em;\n }\n }\n\n .setting-description {\n margin-top: 0.2em;\n margin-bottom: 2em;\n font-size: 70%;\n }\n\n .settings-modal-panel {\n overflow: hidden;\n transition: transform;\n transition-timing-function: ease-in-out;\n transition-duration: 300ms;\n width: 1000px;\n max-width: 90vw;\n height: 90vh;\n\n @media all and (max-width: 800px) {\n max-width: 100vw;\n height: 100%;\n }\n\n >.panel-body {\n height: 100%;\n overflow-y: hidden;\n\n .btn {\n min-height: 2em;\n }\n\n .btn:not(.dropdown-button) {\n padding: 0 2em;\n }\n }\n }\n\n .settings-footer {\n display: flex;\n flex-wrap: wrap;\n line-height: 2;\n\n >* {\n margin-right: 0.5em;\n }\n\n .extra-content {\n display: flex;\n flex-grow: 1;\n }\n }\n\n &.peek {\n .settings-modal-panel {\n /* Explanation:\n * Modal is positioned vertically centered.\n * 100vh - 100% = Distance between modal's top+bottom boundaries and screen\n * (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen\n * + 100% - we move modal completely off-screen, it's top boundary touches\n * bottom of the screen\n * - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible\n */\n transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));\n\n @media all and (max-width: 800px) {\n /* For mobile, the modal takes 100% of the available screen.\n This ensures the minimized modal is always 50px above the browser bottom\n bar regardless of whether or not it is visible.\n */\n transform: translateY(calc(100% - 50px));\n }\n }\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/7962.76663e78ad5ea0bb0b90.css b/priv/static/static/css/7962.76663e78ad5ea0bb0b90.css new file mode 100644 index 000000000..2326ed932 --- /dev/null +++ b/priv/static/static/css/7962.76663e78ad5ea0bb0b90.css @@ -0,0 +1,11 @@ +.importer-uploading{font-size:1.5em;margin:.25em}.exporter-processing{margin:.25em}.autosuggest{position:relative}.autosuggest-input{display:block;width:100%}.autosuggest-results{background-color:#121a24;background-color:var(--bg,#121a24);border:1px solid #222;border-color:var(--border,#222);border-radius:4px;border-radius:var(--inputRadius,4px);border-top-left-radius:0;border-top-right-radius:0;box-shadow:1px 1px 4px rgba(0,0,0,.6);box-shadow:var(--panelShadow);left:0;max-height:400px;overflow-y:auto;position:absolute;right:0;top:100%;z-index:1}.block-card-content-container{margin-top:.5em;text-align:right}.block-card-content-container button{width:10em}.mute-card-content-container{margin-top:.5em;text-align:right}.mute-card-content-container button{width:10em}.domain-mute-card{align-items:center;display:flex;flex:1 0;justify-content:space-between;padding:.6em 1em .6em 0}.domain-mute-card-domain{margin-right:1em;overflow:hidden;text-overflow:ellipsis}.domain-mute-card button{width:10em}.autosuggest-results .domain-mute-card{padding-left:1em}.selectable-list-item-inner{align-items:center;display:flex}.selectable-list-item-inner>*{min-width:0}.selectable-list-item-selected-inner{--faint:var(--selectedMenuFaintText,$fallback--faint);--faintLink:var(--selectedMenuFaintLink,$fallback--faint);--lightText:var(--selectedMenuLightText,$fallback--lightText);--icon:var(--selectedMenuIcon,$fallback--icon);background-color:#151e2a;background-color:var(--selectedMenu,#151e2a);color:var(--selectedMenuText,#b9b9ba)}.selectable-list-header{align-items:center;border-bottom:2px solid #222;border-bottom-color:var(--border,#222);display:flex;padding:.6em 0}.selectable-list-header-actions{flex:1}.selectable-list-checkbox-wrapper{flex:none;padding:0 10px}.with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:1rem}.mutes-and-blocks-tab{height:100%}.mutes-and-blocks-tab .usersearch-wrapper{padding:1em}.mutes-and-blocks-tab .bulk-actions{min-height:2em;padding:0 1em;text-align:right}.mutes-and-blocks-tab .bulk-action-button{width:10em}.mutes-and-blocks-tab .domain-mute-form{display:flex;flex-direction:column;padding:1em}.mutes-and-blocks-tab .domain-mute-button{align-self:flex-end;margin-top:1em;width:10em}.ModifiedIndicator{display:inline-block;position:relative}.modified-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.ProfileSettingIndicator{display:inline-block;position:relative}.profilesetting-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.DraftButtons{display:inline-block;position:relative}.DraftButtons .button-default{margin-left:.5em}.draft-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.mfa-backup-codes .warning{color:orange;color:var(--cOrange,orange)}.mfa-backup-codes .backup-codes{font-family:var(--postCodeFont,monospace)}.mfa-settings .method-item,.mfa-settings .mfa-heading{align-items:baseline;display:flex;flex-wrap:wrap;justify-content:space-between}.mfa-settings .warning{color:orange;color:var(--cOrange,orange)}.mfa-settings .setup-otp{display:flex;flex-wrap:wrap;justify-content:center}.mfa-settings .setup-otp .qr-code{flex:1;padding-right:10px}.mfa-settings .setup-otp .verify{flex:1}.mfa-settings .setup-otp .error{margin:4px 0 0}.mfa-settings .setup-otp .confirm-otp-actions button{margin-top:5px;width:15em} +/*! + * Cropper.js v1.5.13 + * https://fengyuanchen.github.io/cropperjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2022-11-20T05:30:43.444Z + */.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}.image-cropper-img-input{display:none}.image-cropper-image-container{position:relative}.image-cropper-image-container img{display:block;max-width:100%}.image-cropper-buttons-wrapper{margin-top:10px}.image-cropper-buttons-wrapper button{margin-top:5px}.profile-tab .bio{margin:0}.profile-tab .visibility-tray{padding-top:5px}.profile-tab input[type=file]{height:auto;padding:5px}.profile-tab .banner-background-preview{max-width:100%;position:relative;width:300px}.profile-tab .banner-background-preview img{width:100%}.profile-tab .uploading{font-size:1.5em;margin:.25em}.profile-tab .name-changer{width:100%}.profile-tab .current-avatar-container{height:150px;position:relative;width:150px}.profile-tab .current-avatar{border-radius:4px;border-radius:var(--avatarRadius,4px);display:block;height:100%;width:100%}.profile-tab .reset-button{background-color:rgba(0,0,0,.6);border-radius:5px;border-radius:var(--tooltipRadius,5px);cursor:pointer;font-size:1.5em;height:1.5em;line-height:1.5em;opacity:.7;position:absolute;right:.2em;text-align:center;top:.2em;width:1.5em}.profile-tab .reset-button:hover{opacity:1}.profile-tab .reset-button svg{color:#fff}.profile-tab .oauth-tokens{width:100%}.profile-tab .oauth-tokens th{text-align:left}.profile-tab .oauth-tokens .actions{text-align:right}.profile-tab-usersearch-wrapper{padding:1em}.profile-tab-bulk-actions{min-height:2em;padding:0 1em;text-align:right}.profile-tab-bulk-actions button{width:10em}.profile-tab-domain-mute-form{display:flex;flex-direction:column;padding:1em}.profile-tab-domain-mute-form button{align-self:flex-end;margin-top:1em;width:10em}.profile-tab .setting-subitem{margin-left:1.75em}.profile-tab .profile-fields{display:flex}.profile-tab .profile-fields>.emoji-input{flex:1 1 auto;margin:0 .2em .5em;min-width:0}.profile-tab .profile-fields .delete-field{align-self:center;margin:0 .2em .5em;padding:0 .5em;width:20px}.profile-tab .birthday-input{display:block;margin-bottom:1em}.SizeSetting .number-input{max-width:6.5em}.SizeSetting .css-unit-input,.SizeSetting .css-unit-input select{margin-left:.5em;max-width:4em;min-width:4em;width:4em}.column-settings{display:flex;flex-wrap:wrap;justify-content:space-evenly}.column-settings .size-label{display:block;margin-bottom:.5em;margin-top:.5em}.color-input{display:inline-flex}.color-input-field.input{align-items:stretch;display:inline-flex;flex:0 0 0;max-width:9em;padding:.2em 8px}.color-input-field.input input{background:none;border:none;color:#b9b9ba;color:var(--inputText,#b9b9ba);margin:0;padding:0}.color-input-field.input input.textColor{flex:1 0 3em;min-width:3em;padding:0}.color-input-field.input .computedIndicator,.color-input-field.input .transparentIndicator,.color-input-field.input input.nativeColor{align-self:stretch;flex:0 0 2em;min-height:100%;min-width:2em}.color-input-field.input .transparentIndicator{background-color:#f0f;position:relative}.color-input-field.input .transparentIndicator:after,.color-input-field.input .transparentIndicator:before{background-color:#000;content:"";display:block;height:50%;position:absolute;width:50%}.color-input-field.input .transparentIndicator:after{left:0;top:0}.color-input-field.input .transparentIndicator:before{bottom:0;right:0}.color-input .label{flex:1 1 auto}.color-control input.text-input{flex:1;max-width:7em}.shadow-control{display:flex;flex-wrap:wrap;justify-content:center;margin-bottom:1em}.shadow-control .shadow-preview-container,.shadow-control .shadow-tweak{margin:5px 6px 0 0}.shadow-control .shadow-preview-container{display:flex;flex:0;flex-wrap:wrap}.shadow-control .shadow-preview-container input[type=number]{min-width:2em;width:5em}.shadow-control .shadow-preview-container .x-shift-control,.shadow-control .shadow-preview-container .y-shift-control{display:flex;flex:0}.shadow-control .shadow-preview-container .x-shift-control[disabled=disabled] *,.shadow-control .shadow-preview-container .y-shift-control[disabled=disabled] *{opacity:.5}.shadow-control .shadow-preview-container .x-shift-control{align-items:flex-start}.shadow-control .shadow-preview-container .x-shift-control .wrap,.shadow-control .shadow-preview-container input[type=range]{height:2em;margin:0;width:15em}.shadow-control .shadow-preview-container .y-shift-control{align-items:flex-end;flex-direction:column}.shadow-control .shadow-preview-container .y-shift-control .wrap{height:15em;width:2em}.shadow-control .shadow-preview-container .y-shift-control input[type=range]{transform:rotate(90deg);transform-origin:1em 1em}.shadow-control .shadow-preview-container .preview-window{align-items:center;background-color:#999;background-image:linear-gradient(45deg,#666 25%,transparent 0),linear-gradient(-45deg,#666 25%,transparent 0),linear-gradient(45deg,transparent 75%,#666 0),linear-gradient(-45deg,transparent 75%,#666 0);background-position:0 0,0 10px,10px -10px,-10px 0;background-size:20px 20px;border-radius:4px;border-radius:var(--inputRadius,4px);display:flex;flex:1;justify-content:center}.shadow-control .shadow-preview-container .preview-window .preview-block{background-color:#121a24;background-color:var(--bg,#121a24);border-radius:10px;border-radius:var(--panelRadius,10px);height:33%;width:33%}.shadow-control .shadow-tweak{flex:1;min-width:280px}.shadow-control .shadow-tweak .id-control{align-items:stretch}.shadow-control .shadow-tweak .id-control .shadow-switcher{flex:1}.shadow-control .shadow-tweak .id-control .btn,.shadow-control .shadow-tweak .id-control .shadow-switcher{margin-right:5px;min-width:1px}.shadow-control .shadow-tweak .id-control .btn{margin:0 .1em;padding:0 .4em}.font-control input.custom-font{min-width:10em}.font-control.custom .font-switcher{border-bottom-right-radius:0;border-top-right-radius:0}.font-control.custom .custom-font{border-bottom-left-radius:0;border-top-left-radius:0}.contrast-ratio{display:flex;justify-content:flex-end;margin-bottom:5px;margin-top:-4px}.contrast-ratio .label{margin-right:1em}.contrast-ratio .rating{display:inline-block;margin-left:.5em;text-align:center}.preview-container{position:relative}.underlay-preview{bottom:0;left:10px;position:absolute;right:10px;top:0}.theme-tab{padding-bottom:2em}.theme-tab .preset-switcher{margin-right:1em}.theme-tab .btn{margin-left:.25em;margin-right:.25em}.theme-tab .style-control{align-items:baseline;display:flex;margin-bottom:5px}.theme-tab .style-control .label{flex:1}.theme-tab .style-control .opt{margin:.5em}.theme-tab .style-control .color-input{flex:0 0 0}.theme-tab .style-control input,.theme-tab .style-control select{flex:0;margin:0;min-width:3em}.theme-tab .style-control input[type=number],.theme-tab .style-control select[type=number]{min-width:5em}.theme-tab .style-control input[type=range],.theme-tab .style-control select[type=range]{align-self:flex-start;flex:1;min-width:3em}.theme-tab .style-control.disabled input,.theme-tab .style-control.disabled select{opacity:.5}.theme-tab .reset-container{flex-wrap:wrap}.theme-tab .apply-container,.theme-tab .color-container,.theme-tab .fonts-container,.theme-tab .radius-container,.theme-tab .reset-container{display:flex}.theme-tab .fonts-container,.theme-tab .radius-container{flex-direction:column}.theme-tab .color-container{flex-wrap:wrap;justify-content:space-between}.theme-tab .color-container>h4{width:99%}.theme-tab .color-container,.theme-tab .fonts-container,.theme-tab .presets-container,.theme-tab .radius-container,.theme-tab .shadow-container{margin:1em 1em 0}.theme-tab .tab-header{align-items:baseline;display:flex;justify-content:space-between;margin-bottom:1em;min-height:30px;width:100%}.theme-tab .tab-header p{flex:1;margin:0 .5em 0 0}.theme-tab .tab-header-buttons{display:flex;flex-direction:column}.theme-tab .tab-header-buttons .btn{flex:0 auto;margin-bottom:.5em;min-width:1px;padding:0 1em}.theme-tab .shadow-selector .override{flex:1;margin-left:.5em}.theme-tab .shadow-selector .select-container{margin-bottom:-3px;margin-top:-4px}.theme-tab .save-load,.theme-tab .save-load-options{align-items:baseline;display:flex;flex-wrap:wrap;justify-content:center}.theme-tab .save-load .import-export,.theme-tab .save-load .presets,.theme-tab .save-load-options .import-export,.theme-tab .save-load-options .presets{margin-bottom:.5em}.theme-tab .save-load .import-export,.theme-tab .save-load-options .import-export{display:flex}.theme-tab .save-load .override,.theme-tab .save-load-options .override{margin-left:.5em}.theme-tab .save-load-options{flex-wrap:wrap;justify-content:center;margin-top:.5em}.theme-tab .save-load-options .keep-option{margin:0 .5em .5em;min-width:25%}.theme-tab .preview-container{background-color:var(--wallpaper);background-image:var(--body-background-image);background-position:50% 50%;background-size:cover;border-bottom:1px dashed #222;border-color:#222 currentcolor;border-top:1px dashed #222;border-color:var(--border,#222);margin:1em 0;padding:1em}.theme-tab .preview-container .dummy .post{display:flex;font-family:var(--postFont)}.theme-tab .preview-container .dummy .post .content{flex:1}.theme-tab .preview-container .dummy .post .content h4{margin-bottom:.25em}.theme-tab .preview-container .dummy .post .content .icons{display:flex;margin-top:.5em}.theme-tab .preview-container .dummy .post .content .icons i{margin-right:1em}.theme-tab .preview-container .dummy .after-post{align-items:center;display:flex;margin-top:1em}.theme-tab .preview-container .dummy .avatar,.theme-tab .preview-container .dummy .avatar-alt{background:linear-gradient(135deg,#b8e1fc,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd);color:#000;font-family:sans-serif;margin-right:1em;text-align:center}.theme-tab .preview-container .dummy .avatar-alt{border-radius:10px;border-radius:var(--avatarAltRadius,10px);flex:0 auto;font-size:12px;line-height:20px;margin-left:28px;min-height:20px;min-width:20px}.theme-tab .preview-container .dummy .avatar{flex:0 auto;font-size:14px;height:48px;line-height:48px;width:48px}.theme-tab .preview-container .dummy .actions{align-items:baseline;display:flex}.theme-tab .preview-container .dummy .actions .checkbox{align-items:baseline;display:inline-flex;flex:1;margin-right:1em}.theme-tab .preview-container .dummy .separator{border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);margin:1em}.theme-tab .preview-container .dummy .btn{min-width:3em}.theme-tab .radius-item{flex-basis:auto}.theme-tab .color-item,.theme-tab .radius-item{display:flex;flex:1 1 0;flex-direction:column;margin:5px 6px 0 0;min-width:20em}.theme-tab .color-item.wide,.theme-tab .radius-item.wide{min-width:60%}.theme-tab .color-item:not(.wide):nth-child(odd),.theme-tab .radius-item:not(.wide):nth-child(odd){margin-right:7px}.theme-tab .color-item .color,.theme-tab .color-item .opacity,.theme-tab .radius-item .color,.theme-tab .radius-item .opacity{align-items:baseline;display:flex}.theme-tab .theme-color-cl,.theme-tab .theme-radius-rn{align-self:stretch;background:transparent;border:0;box-shadow:none;color:var(--faint,hsla(240,1%,73%,.5))}.theme-tab .theme-color-cl,.theme-tab .theme-color-in,.theme-tab .theme-radius-in{margin-left:4px}.theme-tab .theme-radius-in{flex:1;max-width:7em;min-width:1em}.theme-tab .theme-radius-lb{max-width:50em}.theme-tab .theme-preview-content{padding:20px}.theme-tab .theme-warning{align-items:baseline;display:flex;margin-bottom:.5em}.theme-tab .theme-warning .buttons .btn{margin-bottom:.5em}.extra-content .apply-container{display:flex;flex-direction:row;flex-grow:1;justify-content:space-around}.extra-content .apply-container .btn{flex-grow:1;max-width:10em;min-height:2em;min-width:0;padding:0}.settings_tab-switcher{height:100%}.settings_tab-switcher .setting-item{border-bottom:2px solid var(--fg,#182230);margin:1em 1em 1.4em;padding-bottom:1.4em}.settings_tab-switcher .setting-item>div,.settings_tab-switcher .setting-item>label{display:block;margin-bottom:.5em}.settings_tab-switcher .setting-item>div:last-child,.settings_tab-switcher .setting-item>label:last-child{margin-bottom:0}.settings_tab-switcher .setting-item .select-multiple{display:flex}.settings_tab-switcher .setting-item .select-multiple .option-list{margin:0;padding-left:.5em}.settings_tab-switcher .setting-item:last-child{border-bottom:none;margin-bottom:1em;padding-bottom:0}.settings_tab-switcher .setting-item select{min-width:10em}.settings_tab-switcher .setting-item textarea{height:100px;max-width:100%;width:100%}.settings_tab-switcher .setting-item .unavailable,.settings_tab-switcher .setting-item .unavailable svg{color:var(--cRed,red);color:red} +/*# sourceMappingURL=7962.76663e78ad5ea0bb0b90.css.map*/ \ No newline at end of file diff --git a/priv/static/static/css/7962.76663e78ad5ea0bb0b90.css.map b/priv/static/static/css/7962.76663e78ad5ea0bb0b90.css.map new file mode 100644 index 000000000..9d501f27a --- /dev/null +++ b/priv/static/static/css/7962.76663e78ad5ea0bb0b90.css.map @@ -0,0 +1 @@ +{"version":3,"file":"static/css/7962.76663e78ad5ea0bb0b90.css","mappings":"AAEE,oBACE,gBACA,aCFF,qBACE,aCAJ,aACE,kBAEA,mBACE,cACA,WAGF,qBAME,wBCbW,CDcX,mCAGA,qBCTe,CDUf,gCACA,iBCCoB,sCDCpB,yBACA,0BACA,sCACA,8BAfA,OAGA,iBAaA,gBAjBA,kBAGA,QADA,SAgBA,UE7BJ,8BACE,gBACA,iBAEA,qCACE,WCLJ,6BACE,gBACA,iBAEA,oCACE,WCLJ,kBAIE,mBAFA,aADA,SAEA,8BAEA,wBAEA,yBACE,iBACA,gBACA,uBAGF,yBACE,WAGF,uCACE,iBCfF,4BAEE,mBADA,YACA,CAEA,8BACE,YAIJ,qCAKE,qDAAuD,CACvD,yDAA2D,CAC3D,6DAA+D,CAC/D,8CAA+C,CAP/C,wBJJgB,CIKhB,6CACA,qCAKgD,CAGlD,wBAEE,mBAIA,oEALA,aAEA,cAGA,CAEA,gCACE,OAIJ,kCAEE,UADA,cACA,CCtCF,2BACE,aACA,kBAEA,kCACE,eCNN,sBACE,YAEA,0CACE,YAGF,oCAGE,eADA,cADA,gBAEA,CAGF,0CACE,WAGF,wCAEE,aACA,sBAFA,WAEA,CAGF,0CACE,oBACA,eACA,WCzBJ,mBACE,qBACA,kBAGF,kBACE,gBACA,eACA,kBCRF,yBACE,qBACA,kBAGF,wBACE,gBACA,eACA,kBCRF,cACE,qBACA,kBAEA,8BACE,iBAIJ,eACE,gBACA,eACA,kBCTA,2BACE,YVWgB,CUVhB,4BAGF,gCACE,0CCNF,sDAKE,qBAHA,aACA,eACA,6BACA,CAGF,uBACE,YXGgB,CWFhB,4BAGF,yBACE,aAEA,eADA,sBACA,CAEA,kCACE,OACA,mBAEF,wCACA,+CAGE,qDAEE,eADA,UACA;AChCR;;;;;;;;EAQE,CAEF,mBACE,aAAc,CACd,WAAY,CACZ,aAAc,CACd,iBAAkB,CAEd,iBAAkB,CACtB,wBAAyB,CACtB,qBAAsB,CAEjB,gBACV,CAEA,uBAEY,0BAA2B,CACnC,aAAc,CACd,WAAY,CACZ,sBAAuB,CACvB,yBAA2B,CAC3B,wBAA0B,CAC1B,sBAAwB,CACxB,qBAAuB,CACvB,UACF,CAEF,qFAKE,QAAS,CACT,MAAO,CACP,iBAAkB,CAClB,OAAQ,CACR,KACF,CAEA,kCAEE,eACF,CAEA,kBACE,qBAAsB,CACtB,SACF,CAEA,eACE,qBAAsB,CACtB,UACF,CAEA,kBACE,aAAc,CACd,WAAY,CACZ,sBAAuB,CACvB,kCAAsC,CACtC,eAAgB,CAChB,UACF,CAEA,gBACE,oBAAqB,CACrB,aAAc,CACd,UAAY,CACZ,iBACF,CAEA,yBACI,uBAAwB,CACxB,oBAAqB,CACrB,gBAAsB,CACtB,MAAO,CACP,aAAmB,CACnB,UACF,CAEF,yBACI,qBAAsB,CACtB,sBAAuB,CACvB,WAAY,CACZ,cAAoB,CACpB,KAAM,CACN,eACF,CAEF,gBACE,aAAc,CACd,QAAS,CACT,QAAS,CACT,WAAa,CACb,iBAAkB,CAClB,OAAQ,CACR,OACF,CAEA,6CAEI,qBAAsB,CACtB,WAAY,CACZ,aAAc,CACd,iBACF,CAEF,uBACI,UAAW,CACX,SAAU,CACV,KAAM,CACN,SACF,CAEF,sBACI,UAAW,CACX,MAAO,CACP,QAAS,CACT,SACF,CAEF,2CAGE,aAAc,CACd,WAAY,CACZ,UAAY,CACZ,iBAAkB,CAClB,UACF,CAEA,cACE,qBAAsB,CACtB,MAAO,CACP,KACF,CAEA,cACE,qBACF,CAEA,qBACI,gBAAiB,CACjB,UAAW,CACX,KAAM,CACN,SACF,CAEF,qBACI,gBAAiB,CACjB,UAAW,CACX,MAAO,CACP,QACF,CAEF,qBACI,gBAAiB,CACjB,SAAU,CACV,KAAM,CACN,SACF,CAEF,qBACI,WAAY,CACZ,gBAAiB,CACjB,UAAW,CACX,MACF,CAEF,eACE,qBAAsB,CACtB,UAAW,CACX,WAAa,CACb,SACF,CAEA,uBACI,gBAAiB,CACjB,eAAgB,CAChB,UAAW,CACX,OACF,CAEF,uBACI,gBAAiB,CACjB,QAAS,CACT,gBAAiB,CACjB,QACF,CAEF,uBACI,gBAAiB,CACjB,SAAU,CACV,eAAgB,CAChB,OACF,CAEF,uBACI,WAAY,CACZ,eAAgB,CAChB,QAAS,CACT,gBACF,CAEF,wBACI,kBAAmB,CACnB,UAAW,CACX,QACF,CAEF,wBACI,kBAAmB,CACnB,SAAU,CACV,QACF,CAEF,wBACI,WAAY,CACZ,kBAAmB,CACnB,SACF,CAEF,wBACI,WAAY,CACZ,kBAAmB,CACnB,WAAY,CACZ,SAAU,CACV,UAAW,CACX,UACF,CAEF,yBAEA,wBACM,WAAY,CACZ,UACJ,CACE,CAEJ,yBAEA,wBACM,WAAY,CACZ,UACJ,CACE,CAEJ,0BAEA,wBACM,UAAW,CACX,WAAa,CACb,SACJ,CACE,CAEJ,+BACI,qBAAsB,CACtB,WAAY,CACZ,WAAY,CACZ,aAAc,CACd,WAAY,CACZ,SAAU,CACV,iBAAkB,CAClB,UAAW,CACX,UACF,CAEF,mBACE,SACF,CAEA,YACE,4QACF,CAEA,cACE,aAAc,CACd,QAAS,CACT,iBAAkB,CAClB,OACF,CAEA,gBACE,sBACF,CAEA,cACE,WACF,CAEA,cACE,gBACF,CAEA,qIAIE,kBACF,CClTE,yBACE,aAGF,+BACE,kBAEA,mCACE,cACA,eAIJ,+BACE,gBAEA,sCACE,eChBJ,kBACE,SAGF,8BACE,gBAGF,8BAEE,YADA,WACA,CAGF,wCACE,eAEA,kBADA,WACA,CAEA,4CACE,WAIJ,wBACE,gBACA,aAGF,2BACE,WAGF,uCAGE,aAFA,kBACA,WACA,CAGF,6BAIE,iBdnBqB,CcoBrB,sCAJA,cAEA,YADA,UAGA,CAGF,2BAME,gCAFA,iBd5BsB,Cc6BtB,uCAQA,eADA,gBAHA,aAEA,kBAJA,WANA,kBAEA,WAOA,kBARA,SAMA,WAKA,CAEA,iCACE,UAGF,+BACE,WAIJ,2BACE,WAEA,8BACE,gBAGF,oCACE,iBAIJ,gCACE,YAGF,0BAGE,eADA,cADA,gBAEA,CAEA,iCACE,WAIJ,8BAEE,aACA,sBAFA,WAEA,CAEA,qCACE,oBACA,eACA,WAIJ,8BACE,mBAGF,6BACE,aAEA,0CACE,cACA,mBACA,YAGF,2CAEE,kBACA,mBACA,eAHA,UAGA,CAIJ,6BACE,cACA,kBCpIF,2BACE,gBAGF,iEAEE,iBAEA,cACA,cAFA,SAEA,CCVJ,iBACE,aAEA,eADA,4BACA,CAGF,6BACE,cACA,mBACA,gBCRF,aACE,oBAEA,yBAIE,oBAHA,oBACA,WACA,cAEA,iBAEA,+BACE,gBAGA,YAFA,ajBHgB,CiBIhB,+BAGA,QAAO,CADP,SACA,CAEA,yCACE,aACA,cACA,UAWJ,sIAIE,mBAFA,aAGA,gBAFA,aAEA,CAGF,+CAEE,sBACA,kBAEA,2GAIE,sBADA,WADA,cAIA,WADA,kBAEA,UAGF,qDAEE,MAAK,CADL,KACA,CAGF,sDACE,SACA,QAKN,oBACE,cCpEF,gCAEE,MAAK,CADL,aACA,CCDJ,gBACE,aACA,eACA,uBACA,kBAEA,wEAEE,mBAGF,0CAEE,aADA,OAEA,eAIA,6DAEE,cADA,SACA,CAGF,sHAEE,aACA,OAEA,gKACE,WAIJ,2DACE,uBAGF,6HAIE,WAFA,SACA,UACA,CAGF,2DAEE,qBADA,qBACA,CAEA,iEAEE,YADA,SAjCG,CAqCL,6EAEE,wBADA,wBACA,CAIJ,0DAIE,mBAFA,sBAIA,0MACE,CAKF,kDADA,0BAEA,iBnBnDkB,CmBoDlB,qCAXA,aAFA,OAIA,sBASA,CAEA,yEAGE,wBnB7EO,CmB8EP,mCACA,kBnB9DgB,CmB+DhB,sCAJA,WADA,SAKA,CAKN,8BACE,OACA,gBAEA,0CACE,oBAEA,2DACE,OAGF,0GAGE,iBADA,aACA,CAGF,+CAEE,cADA,cACA,CCxGN,gCACE,eAKA,oCAEE,4BAA2B,CAD3B,yBACA,CAGF,kCAEE,2BAA0B,CAD1B,wBACA,CChBN,gBACE,aACA,yBAEA,kBADA,eACA,CAEA,uBACE,iBAGF,wBACE,qBAEA,iBADA,iBACA,CCbJ,mBACE,kBAGF,kBAGE,SACA,UAHA,kBAIA,WAHA,KAGA,CCRF,WACE,mBAEA,4BACE,iBAGF,gBACE,kBACA,mBAGF,0BAEE,qBADA,aAEA,kBAEA,iCACE,OAGF,+BACE,YAGF,uCACE,WAGF,iEAIE,MAAK,CADL,SADA,aAEA,CAEA,2FACE,cAGF,yFAGE,sBAFA,OACA,aACA,CAKF,mFAEE,WAKN,4BACE,eAGF,6IAKE,aAGF,yDAEE,sBAGF,4BAKE,eACA,8BALA,+BACE,UAOJ,gJAKE,iBAGF,uBAGE,qBAFA,aACA,8BAIA,kBADA,gBADA,UAEA,CAEA,yBACE,OAEA,kBAIJ,+BACE,aACA,sBAEA,oCAEE,YAEA,mBAHA,cAEA,aACA,CAKF,sCACE,OACA,iBAGF,8CAEE,mBADA,eACA,CAIJ,oDAIE,qBAFA,aAGA,eAFA,sBAEA,CAEA,wJAEE,mBAGF,kFACE,aAGF,wEACE,iBAIJ,8BACE,eAEA,uBADA,eACA,CAEA,2CACE,mBACA,cAIJ,8BAOE,kCACA,8CAEA,4BADA,sBANA,6BvBxJe,CuBwJf,8BvBxJe,CuBwJf,0BvBxJe,CuByJf,gCACA,aACA,WAIA,CAGE,2CAEE,aADA,2BACA,CAEA,oDACE,OAEA,uDACE,oBAGF,2DAEE,aADA,eACA,CAEA,6DACE,iBAMR,iDAGE,mBADA,aADA,cAEA,CAGF,8FAEE,0HACE,CAWF,WACA,uBAEA,iBADA,iBACA,CAGF,iDAOE,kBvB1MoB,CuB2MpB,0CAPA,YAEA,eAGA,iBAJA,iBAGA,gBADA,cAIA,CAGF,6CACE,YAGA,eADA,YAEA,iBAHA,UAGA,CAGF,8CAEE,qBADA,YACA,CAEA,wDAEE,qBADA,oBAGA,MAAK,CADL,gBACA,CAIJ,gDAGE,uBvBpPW,CuBoPX,iBvBpPW,CuBqPX,gCAHA,UAGA,CAGF,0CACE,cAKN,wBACE,gBAGF,+CAIE,aAEA,WADA,sBAFA,mBADA,cAIA,CAEA,yDACE,cAGF,mGACE,iBAGF,8HAGE,qBADA,YACA,CAIJ,uDAME,mBAFA,uBAFA,SACA,gBAEA,sCACA,CAGF,kFAGE,gBAGF,4BAGE,MAAK,CADL,cADA,aAEA,CAGF,4BACE,eAGF,kCACE,aAGF,0BAEE,qBADA,aAEA,mBAGE,wCACE,mBAON,gCACE,aACA,mBAEA,WAAU,CADV,4BACA,CAGA,qCACE,YAGA,eAFA,eACA,YAEA,UC1VN,uBACE,YAEA,qCACE,0CACA,qBACA,qBAEA,oFAEE,cACA,mBAEA,0GACE,gBAIJ,sDACE,aAEA,mEACE,SACA,kBAIJ,gDACE,mBAEA,kBADA,gBACA,CAGF,4CACE,eAGF,8CAGE,aADA,eADA,UAEA,CAGF,wGAEE,sBACA,SxBnCW","sources":["webpack://pleroma_fe/./src/components/importer/importer.vue","webpack://pleroma_fe/./src/components/exporter/exporter.vue","webpack://pleroma_fe/./src/components/autosuggest/autosuggest.vue","webpack://pleroma_fe/./src/_variables.scss","webpack://pleroma_fe/./src/components/block_card/block_card.vue","webpack://pleroma_fe/./src/components/mute_card/mute_card.vue","webpack://pleroma_fe/./src/components/domain_mute_card/domain_mute_card.vue","webpack://pleroma_fe/./src/components/selectable_list/selectable_list.vue","webpack://pleroma_fe/./src/hocs/with_subscription/with_subscription.scss","webpack://pleroma_fe/./src/components/settings_modal/tabs/mutes_and_blocks_tab.scss","webpack://pleroma_fe/./src/components/settings_modal/helpers/modified_indicator.vue","webpack://pleroma_fe/./src/components/settings_modal/helpers/profile_setting_indicator.vue","webpack://pleroma_fe/./src/components/settings_modal/helpers/draft_buttons.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/security_tab/mfa.vue","webpack://pleroma_fe/./node_modules/cropperjs/dist/cropper.css","webpack://pleroma_fe/./src/components/image_cropper/image_cropper.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/profile_tab.scss","webpack://pleroma_fe/./src/components/settings_modal/helpers/size_setting.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/general_tab.vue","webpack://pleroma_fe/./src/components/color_input/color_input.scss","webpack://pleroma_fe/./src/components/color_input/color_input.vue","webpack://pleroma_fe/./src/components/shadow_control/shadow_control.vue","webpack://pleroma_fe/./src/components/font_control/font_control.vue","webpack://pleroma_fe/./src/components/contrast_ratio/contrast_ratio.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/theme_tab/preview.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/theme_tab/theme_tab.scss","webpack://pleroma_fe/./src/components/settings_modal/settings_modal_user_content.scss"],"sourcesContent":["\n.importer {\n &-uploading {\n font-size: 1.5em;\n margin: 0.25em;\n }\n}\n","\n.exporter {\n &-processing {\n margin: 0.25em;\n }\n}\n","\n@import \"../../variables\";\n\n.autosuggest {\n position: relative;\n\n &-input {\n display: block;\n width: 100%;\n }\n\n &-results {\n position: absolute;\n left: 0;\n top: 100%;\n right: 0;\n max-height: 400px;\n background-color: $fallback--bg;\n background-color: var(--bg, $fallback--bg);\n border-style: solid;\n border-width: 1px;\n border-color: $fallback--border;\n border-color: var(--border, $fallback--border);\n border-radius: $fallback--inputRadius;\n border-radius: var(--inputRadius, $fallback--inputRadius);\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);\n box-shadow: var(--panelShadow);\n overflow-y: auto;\n z-index: 1;\n }\n}\n","$main-color: #f58d2c;\n$main-background: white;\n$darkened-background: whitesmoke;\n\n$fallback--bg: #121a24;\n$fallback--fg: #182230;\n$fallback--faint: rgb(185 185 186 / 50%);\n$fallback--text: #b9b9ba;\n$fallback--link: #d8a070;\n$fallback--icon: #666;\n$fallback--lightBg: rgb(21 30 42);\n$fallback--lightText: #b9b9ba;\n$fallback--border: #222;\n$fallback--cRed: #f00;\n$fallback--cBlue: #0095ff;\n$fallback--cGreen: #0fa00f;\n$fallback--cOrange: orange;\n\n$fallback--alertError: rgb(211 16 20 / 50%);\n$fallback--alertWarning: rgb(111 111 20 / 50%);\n\n$fallback--panelRadius: 10px;\n$fallback--checkboxRadius: 2px;\n$fallback--btnRadius: 4px;\n$fallback--inputRadius: 4px;\n$fallback--tooltipRadius: 5px;\n$fallback--avatarRadius: 4px;\n$fallback--avatarAltRadius: 10px;\n$fallback--attachmentRadius: 10px;\n$fallback--chatMessageRadius: 10px;\n\n$fallback--buttonShadow: 0 0 2px 0 rgb(0 0 0 / 100%),\n 0 1px 0 0 rgb(255 255 255 / 20%) inset,\n 0 -1px 0 0 rgb(0 0 0 / 20%) inset;\n\n$status-margin: 0.75em;\n","\n.block-card-content-container {\n margin-top: 0.5em;\n text-align: right;\n\n button {\n width: 10em;\n }\n}\n","\n.mute-card-content-container {\n margin-top: 0.5em;\n text-align: right;\n\n button {\n width: 10em;\n }\n}\n","\n.domain-mute-card {\n flex: 1 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0.6em 1em 0.6em 0;\n\n &-domain {\n margin-right: 1em;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n button {\n width: 10em;\n }\n\n .autosuggest-results & {\n padding-left: 1em;\n }\n}\n","\n@import \"../../variables\";\n\n.selectable-list {\n &-item-inner {\n display: flex;\n align-items: center;\n\n > * {\n min-width: 0;\n }\n }\n\n &-item-selected-inner {\n background-color: $fallback--lightBg;\n background-color: var(--selectedMenu, $fallback--lightBg);\n color: var(--selectedMenuText, $fallback--text);\n\n --faint: var(--selectedMenuFaintText, $fallback--faint);\n --faintLink: var(--selectedMenuFaintLink, $fallback--faint);\n --lightText: var(--selectedMenuLightText, $fallback--lightText);\n --icon: var(--selectedMenuIcon, $fallback--icon);\n }\n\n &-header {\n display: flex;\n align-items: center;\n padding: 0.6em 0;\n border-bottom: 2px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n\n &-actions {\n flex: 1;\n }\n }\n\n &-checkbox-wrapper {\n padding: 0 10px;\n flex: none;\n }\n}\n",".with-subscription {\n &-loading {\n padding: 10px;\n text-align: center;\n\n .error {\n font-size: 1rem;\n }\n }\n}\n",".mutes-and-blocks-tab {\n height: 100%;\n\n .usersearch-wrapper {\n padding: 1em;\n }\n\n .bulk-actions {\n text-align: right;\n padding: 0 1em;\n min-height: 2em;\n }\n\n .bulk-action-button {\n width: 10em;\n }\n\n .domain-mute-form {\n padding: 1em;\n display: flex;\n flex-direction: column;\n }\n\n .domain-mute-button {\n align-self: flex-end;\n margin-top: 1em;\n width: 10em;\n }\n}\n","\n.ModifiedIndicator {\n display: inline-block;\n position: relative;\n}\n\n.modified-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n.ProfileSettingIndicator {\n display: inline-block;\n position: relative;\n}\n\n.profilesetting-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n.DraftButtons {\n display: inline-block;\n position: relative;\n\n .button-default {\n margin-left: 0.5em;\n }\n}\n\n.draft-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n@import \"../../../../variables\";\n\n.mfa-backup-codes {\n .warning {\n color: $fallback--cOrange;\n color: var(--cOrange, $fallback--cOrange);\n }\n\n .backup-codes {\n font-family: var(--postCodeFont, monospace);\n }\n}\n","\n@import \"../../../../variables\";\n\n.mfa-settings {\n .mfa-heading,\n .method-item {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: baseline;\n }\n\n .warning {\n color: $fallback--cOrange;\n color: var(--cOrange, $fallback--cOrange);\n }\n\n .setup-otp {\n display: flex;\n justify-content: center;\n flex-wrap: wrap;\n\n .qr-code {\n flex: 1;\n padding-right: 10px;\n }\n .verify { flex: 1; }\n .error { margin: 4px 0 0; }\n\n .confirm-otp-actions {\n button {\n width: 15em;\n margin-top: 5px;\n }\n }\n }\n}\n","/*!\n * Cropper.js v1.5.13\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2022-11-20T05:30:43.444Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n }\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: 0.5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline: 1px solid #39f;\n outline-color: rgba(51, 153, 255, 75%);\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: 0.5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n }\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n }\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: 0.75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center::before,\n .cropper-center::after {\n background-color: #eee;\n content: \" \";\n display: block;\n position: absolute;\n }\n\n.cropper-center::before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n }\n\n.cropper-center::after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n }\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: 0.1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n }\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n }\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n }\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n }\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: 0.75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n }\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n }\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n }\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n }\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n }\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n }\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n }\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n }\n\n@media (min-width: 768px) {\n\n.cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n }\n\n@media (min-width: 992px) {\n\n.cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n }\n\n@media (min-width: 1200px) {\n\n.cropper-point.point-se {\n height: 5px;\n opacity: 0.75;\n width: 5px;\n }\n }\n\n.cropper-point.point-se::before {\n background-color: #39f;\n bottom: -50%;\n content: \" \";\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n }\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC\");\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n","\n.image-cropper {\n &-img-input {\n display: none;\n }\n\n &-image-container {\n position: relative;\n\n img {\n display: block;\n max-width: 100%;\n }\n }\n\n &-buttons-wrapper {\n margin-top: 10px;\n\n button {\n margin-top: 5px;\n }\n }\n}\n","@import \"../../../variables\";\n\n.profile-tab {\n .bio {\n margin: 0;\n }\n\n .visibility-tray {\n padding-top: 5px;\n }\n\n input[type=\"file\"] {\n padding: 5px;\n height: auto;\n }\n\n .banner-background-preview {\n max-width: 100%;\n width: 300px;\n position: relative;\n\n img {\n width: 100%;\n }\n }\n\n .uploading {\n font-size: 1.5em;\n margin: 0.25em;\n }\n\n .name-changer {\n width: 100%;\n }\n\n .current-avatar-container {\n position: relative;\n width: 150px;\n height: 150px;\n }\n\n .current-avatar {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: $fallback--avatarRadius;\n border-radius: var(--avatarRadius, $fallback--avatarRadius);\n }\n\n .reset-button {\n position: absolute;\n top: 0.2em;\n right: 0.2em;\n border-radius: $fallback--tooltipRadius;\n border-radius: var(--tooltipRadius, $fallback--tooltipRadius);\n background-color: rgb(0 0 0 / 60%);\n opacity: 0.7;\n width: 1.5em;\n height: 1.5em;\n text-align: center;\n line-height: 1.5em;\n font-size: 1.5em;\n cursor: pointer;\n\n &:hover {\n opacity: 1;\n }\n\n svg {\n color: white;\n }\n }\n\n .oauth-tokens {\n width: 100%;\n\n th {\n text-align: left;\n }\n\n .actions {\n text-align: right;\n }\n }\n\n &-usersearch-wrapper {\n padding: 1em;\n }\n\n &-bulk-actions {\n text-align: right;\n padding: 0 1em;\n min-height: 2em;\n\n button {\n width: 10em;\n }\n }\n\n &-domain-mute-form {\n padding: 1em;\n display: flex;\n flex-direction: column;\n\n button {\n align-self: flex-end;\n margin-top: 1em;\n width: 10em;\n }\n }\n\n .setting-subitem {\n margin-left: 1.75em;\n }\n\n .profile-fields {\n display: flex;\n\n & > .emoji-input {\n flex: 1 1 auto;\n margin: 0 0.2em 0.5em;\n min-width: 0;\n }\n\n .delete-field {\n width: 20px;\n align-self: center;\n margin: 0 0.2em 0.5em;\n padding: 0 0.5em;\n }\n }\n\n .birthday-input {\n display: block;\n margin-bottom: 1em;\n }\n}\n","\n.SizeSetting {\n .number-input {\n max-width: 6.5em;\n }\n\n .css-unit-input,\n .css-unit-input select {\n margin-left: 0.5em;\n width: 4em;\n max-width: 4em;\n min-width: 4em;\n }\n}\n\n","\n.column-settings {\n display: flex;\n justify-content: space-evenly;\n flex-wrap: wrap;\n}\n\n.column-settings .size-label {\n display: block;\n margin-bottom: 0.5em;\n margin-top: 0.5em;\n}\n","@import \"../../variables\";\n\n.color-input {\n display: inline-flex;\n\n &-field.input {\n display: inline-flex;\n flex: 0 0 0;\n max-width: 9em;\n align-items: stretch;\n padding: 0.2em 8px;\n\n input {\n background: none;\n color: $fallback--lightText;\n color: var(--inputText, $fallback--lightText);\n border: none;\n padding: 0;\n margin: 0;\n\n &.textColor {\n flex: 1 0 3em;\n min-width: 3em;\n padding: 0;\n }\n\n &.nativeColor {\n flex: 0 0 2em;\n min-width: 2em;\n align-self: stretch;\n min-height: 100%;\n }\n }\n\n .computedIndicator,\n .transparentIndicator {\n flex: 0 0 2em;\n min-width: 2em;\n align-self: stretch;\n min-height: 100%;\n }\n\n .transparentIndicator {\n // forgot to install counter-strike source, ooops\n background-color: #f0f;\n position: relative;\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n background-color: #000;\n position: absolute;\n height: 50%;\n width: 50%;\n }\n\n &::after {\n top: 0;\n left: 0;\n }\n\n &::before {\n bottom: 0;\n right: 0;\n }\n }\n }\n\n .label {\n flex: 1 1 auto;\n }\n}\n","\n.color-control {\n input.text-input {\n max-width: 7em;\n flex: 1;\n }\n}\n","\n@import \"../../variables\";\n\n.shadow-control {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n margin-bottom: 1em;\n\n .shadow-preview-container,\n .shadow-tweak {\n margin: 5px 6px 0 0;\n }\n\n .shadow-preview-container {\n flex: 0;\n display: flex;\n flex-wrap: wrap;\n\n $side: 15em;\n\n input[type=\"number\"] {\n width: 5em;\n min-width: 2em;\n }\n\n .x-shift-control,\n .y-shift-control {\n display: flex;\n flex: 0;\n\n &[disabled=\"disabled\"] * {\n opacity: 0.5;\n }\n }\n\n .x-shift-control {\n align-items: flex-start;\n }\n\n .x-shift-control .wrap,\n input[type=\"range\"] {\n margin: 0;\n width: $side;\n height: 2em;\n }\n\n .y-shift-control {\n flex-direction: column;\n align-items: flex-end;\n\n .wrap {\n width: 2em;\n height: $side;\n }\n\n input[type=\"range\"] {\n transform-origin: 1em 1em;\n transform: rotate(90deg);\n }\n }\n\n .preview-window {\n flex: 1;\n background-color: #999;\n display: flex;\n align-items: center;\n justify-content: center;\n background-image:\n linear-gradient(45deg, #666 25%, transparent 25%),\n linear-gradient(-45deg, #666 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #666 75%),\n linear-gradient(-45deg, transparent 75%, #666 75%);\n background-size: 20px 20px;\n background-position: 0 0, 0 10px, 10px -10px, -10px 0;\n border-radius: $fallback--inputRadius;\n border-radius: var(--inputRadius, $fallback--inputRadius);\n\n .preview-block {\n width: 33%;\n height: 33%;\n background-color: $fallback--bg;\n background-color: var(--bg, $fallback--bg);\n border-radius: $fallback--panelRadius;\n border-radius: var(--panelRadius, $fallback--panelRadius);\n }\n }\n }\n\n .shadow-tweak {\n flex: 1;\n min-width: 280px;\n\n .id-control {\n align-items: stretch;\n\n .shadow-switcher {\n flex: 1;\n }\n\n .shadow-switcher,\n .btn {\n min-width: 1px;\n margin-right: 5px;\n }\n\n .btn {\n padding: 0 0.4em;\n margin: 0 0.1em;\n }\n }\n }\n}\n","\n@import \"../../variables\";\n\n.font-control {\n input.custom-font {\n min-width: 10em;\n }\n\n &.custom {\n /* TODO Should make proper joiners... */\n .font-switcher {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n\n .custom-font {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n }\n}\n","\n.contrast-ratio {\n display: flex;\n justify-content: flex-end;\n margin-top: -4px;\n margin-bottom: 5px;\n\n .label {\n margin-right: 1em;\n }\n\n .rating {\n display: inline-block;\n text-align: center;\n margin-left: 0.5em;\n }\n}\n","\n.preview-container {\n position: relative;\n}\n\n.underlay-preview {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 10px;\n right: 10px;\n}\n","@import \"src/variables\";\n\n.theme-tab {\n padding-bottom: 2em;\n\n .preset-switcher {\n margin-right: 1em;\n }\n\n .btn {\n margin-left: 0.25em;\n margin-right: 0.25em;\n }\n\n .style-control {\n display: flex;\n align-items: baseline;\n margin-bottom: 5px;\n\n .label {\n flex: 1;\n }\n\n .opt {\n margin: 0.5em;\n }\n\n .color-input {\n flex: 0 0 0;\n }\n\n input,\n select {\n min-width: 3em;\n margin: 0;\n flex: 0;\n\n &[type=\"number\"] {\n min-width: 5em;\n }\n\n &[type=\"range\"] {\n flex: 1;\n min-width: 3em;\n align-self: flex-start;\n }\n }\n\n &.disabled {\n input,\n select {\n opacity: 0.5;\n }\n }\n }\n\n .reset-container {\n flex-wrap: wrap;\n }\n\n .fonts-container,\n .reset-container,\n .apply-container,\n .radius-container,\n .color-container, {\n display: flex;\n }\n\n .fonts-container,\n .radius-container {\n flex-direction: column;\n }\n\n .color-container {\n > h4 {\n width: 99%;\n }\n\n flex-wrap: wrap;\n justify-content: space-between;\n }\n\n .fonts-container,\n .color-container,\n .shadow-container,\n .radius-container,\n .presets-container {\n margin: 1em 1em 0;\n }\n\n .tab-header {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n width: 100%;\n min-height: 30px;\n margin-bottom: 1em;\n\n p {\n flex: 1;\n margin: 0;\n margin-right: 0.5em;\n }\n }\n\n .tab-header-buttons {\n display: flex;\n flex-direction: column;\n\n .btn {\n min-width: 1px;\n flex: 0 auto;\n padding: 0 1em;\n margin-bottom: 0.5em;\n }\n }\n\n .shadow-selector {\n .override {\n flex: 1;\n margin-left: 0.5em;\n }\n\n .select-container {\n margin-top: -4px;\n margin-bottom: -3px;\n }\n }\n\n .save-load,\n .save-load-options {\n display: flex;\n justify-content: center;\n align-items: baseline;\n flex-wrap: wrap;\n\n .presets,\n .import-export {\n margin-bottom: 0.5em;\n }\n\n .import-export {\n display: flex;\n }\n\n .override {\n margin-left: 0.5em;\n }\n }\n\n .save-load-options {\n flex-wrap: wrap;\n margin-top: 0.5em;\n justify-content: center;\n\n .keep-option {\n margin: 0 0.5em 0.5em;\n min-width: 25%;\n }\n }\n\n .preview-container {\n border-top: 1px dashed;\n border-bottom: 1px dashed;\n border-color: $fallback--border;\n border-color: var(--border, $fallback--border);\n margin: 1em 0;\n padding: 1em;\n background-color: var(--wallpaper);\n background-image: var(--body-background-image);\n background-size: cover;\n background-position: 50% 50%;\n\n .dummy {\n .post {\n font-family: var(--postFont);\n display: flex;\n\n .content {\n flex: 1;\n\n h4 {\n margin-bottom: 0.25em;\n }\n\n .icons {\n margin-top: 0.5em;\n display: flex;\n\n i {\n margin-right: 1em;\n }\n }\n }\n }\n\n .after-post {\n margin-top: 1em;\n display: flex;\n align-items: center;\n }\n\n .avatar,\n .avatar-alt {\n background:\n linear-gradient(\n 135deg,\n #b8e1fc 0%,\n #a9d2f3 10%,\n #90bae4 25%,\n #90bcea 37%,\n #90bff0 50%,\n #6ba8e5 51%,\n #a2daf5 83%,\n #bdf3fd 100%\n );\n color: black;\n font-family: sans-serif;\n text-align: center;\n margin-right: 1em;\n }\n\n .avatar-alt {\n flex: 0 auto;\n margin-left: 28px;\n font-size: 12px;\n min-width: 20px;\n min-height: 20px;\n line-height: 20px;\n border-radius: $fallback--avatarAltRadius;\n border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);\n }\n\n .avatar {\n flex: 0 auto;\n width: 48px;\n height: 48px;\n font-size: 14px;\n line-height: 48px;\n }\n\n .actions {\n display: flex;\n align-items: baseline;\n\n .checkbox {\n display: inline-flex;\n align-items: baseline;\n margin-right: 1em;\n flex: 1;\n }\n }\n\n .separator {\n margin: 1em;\n border-bottom: 1px solid;\n border-color: $fallback--border;\n border-color: var(--border, $fallback--border);\n }\n\n .btn {\n min-width: 3em;\n }\n }\n }\n\n .radius-item {\n flex-basis: auto;\n }\n\n .radius-item,\n .color-item {\n min-width: 20em;\n margin: 5px 6px 0 0;\n display: flex;\n flex-direction: column;\n flex: 1 1 0;\n\n &.wide {\n min-width: 60%;\n }\n\n &:not(.wide):nth-child(2n+1) {\n margin-right: 7px;\n }\n\n .color,\n .opacity {\n display: flex;\n align-items: baseline;\n }\n }\n\n .theme-radius-rn,\n .theme-color-cl {\n border: 0;\n box-shadow: none;\n background: transparent;\n color: var(--faint, $fallback--faint);\n align-self: stretch;\n }\n\n .theme-color-cl,\n .theme-radius-in,\n .theme-color-in {\n margin-left: 4px;\n }\n\n .theme-radius-in {\n min-width: 1em;\n max-width: 7em;\n flex: 1;\n }\n\n .theme-radius-lb {\n max-width: 50em;\n }\n\n .theme-preview-content {\n padding: 20px;\n }\n\n .theme-warning {\n display: flex;\n align-items: baseline;\n margin-bottom: 0.5em;\n\n .buttons {\n .btn {\n margin-bottom: 0.5em;\n }\n }\n }\n}\n\n.extra-content {\n .apply-container {\n display: flex;\n flex-direction: row;\n justify-content: space-around;\n flex-grow: 1;\n\n /* stylelint-disable-next-line no-descending-specificity */\n .btn {\n flex-grow: 1;\n min-height: 2em;\n min-width: 0;\n max-width: 10em;\n padding: 0;\n }\n }\n}\n","@import \"src/variables\";\n\n.settings_tab-switcher {\n height: 100%;\n\n .setting-item {\n border-bottom: 2px solid var(--fg, $fallback--fg);\n margin: 1em 1em 1.4em;\n padding-bottom: 1.4em;\n\n > div,\n > label {\n display: block;\n margin-bottom: 0.5em;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n .select-multiple {\n display: flex;\n\n .option-list {\n margin: 0;\n padding-left: 0.5em;\n }\n }\n\n &:last-child {\n border-bottom: none;\n padding-bottom: 0;\n margin-bottom: 1em;\n }\n\n select {\n min-width: 10em;\n }\n\n textarea {\n width: 100%;\n max-width: 100%;\n height: 100px;\n }\n\n .unavailable,\n .unavailable svg {\n color: var(--cRed, $fallback--cRed);\n color: $fallback--cRed;\n }\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/9114.8def3b2b7fe70b3b3712.css b/priv/static/static/css/9114.8def3b2b7fe70b3b3712.css deleted file mode 100644 index 6c25e9030..000000000 --- a/priv/static/static/css/9114.8def3b2b7fe70b3b3712.css +++ /dev/null @@ -1,11 +0,0 @@ -.importer-uploading{font-size:1.5em;margin:.25em}.exporter-processing{margin:.25em}.autosuggest{position:relative}.autosuggest-input{display:block;width:100%}.autosuggest-results{background-color:#121a24;background-color:var(--bg,#121a24);border:1px solid #222;border-color:var(--border,#222);border-radius:4px;border-radius:var(--inputRadius,4px);border-top-left-radius:0;border-top-right-radius:0;box-shadow:1px 1px 4px rgba(0,0,0,.6);box-shadow:var(--panelShadow);left:0;max-height:400px;overflow-y:auto;position:absolute;right:0;top:100%;z-index:1}.block-card-content-container{margin-top:.5em;text-align:right}.block-card-content-container button{width:10em}.mute-card-content-container{margin-top:.5em;text-align:right}.mute-card-content-container button{width:10em}.domain-mute-card{align-items:center;display:flex;flex:1 0;justify-content:space-between;padding:.6em 1em .6em 0}.domain-mute-card-domain{margin-right:1em;overflow:hidden;text-overflow:ellipsis}.domain-mute-card button{width:10em}.autosuggest-results .domain-mute-card{padding-left:1em}.selectable-list-item-inner{align-items:center;display:flex}.selectable-list-item-inner>*{min-width:0}.selectable-list-item-selected-inner{--faint:var(--selectedMenuFaintText,$fallback--faint);--faintLink:var(--selectedMenuFaintLink,$fallback--faint);--lightText:var(--selectedMenuLightText,$fallback--lightText);--icon:var(--selectedMenuIcon,$fallback--icon);background-color:#151e2a;background-color:var(--selectedMenu,#151e2a);color:var(--selectedMenuText,#b9b9ba)}.selectable-list-header{align-items:center;border-bottom:2px solid #222;border-bottom-color:var(--border,#222);display:flex;padding:.6em 0}.selectable-list-header-actions{flex:1}.selectable-list-checkbox-wrapper{flex:none;padding:0 10px}.with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:1rem}.mutes-and-blocks-tab{height:100%}.mutes-and-blocks-tab .usersearch-wrapper{padding:1em}.mutes-and-blocks-tab .bulk-actions{min-height:2em;padding:0 1em;text-align:right}.mutes-and-blocks-tab .bulk-action-button{width:10em}.mutes-and-blocks-tab .domain-mute-form{display:flex;flex-direction:column;padding:1em}.mutes-and-blocks-tab .domain-mute-button{align-self:flex-end;margin-top:1em;width:10em}.ModifiedIndicator{display:inline-block;position:relative}.modified-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.ServerSideIndicator{display:inline-block;position:relative}.serverside-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.mfa-backup-codes .warning{color:orange;color:var(--cOrange,orange)}.mfa-backup-codes .backup-codes{font-family:var(--postCodeFont,monospace)}.mfa-settings .method-item,.mfa-settings .mfa-heading{align-items:baseline;display:flex;flex-wrap:wrap;justify-content:space-between}.mfa-settings .warning{color:orange;color:var(--cOrange,orange)}.mfa-settings .setup-otp{display:flex;flex-wrap:wrap;justify-content:center}.mfa-settings .setup-otp .qr-code{flex:1;padding-right:10px}.mfa-settings .setup-otp .verify{flex:1}.mfa-settings .setup-otp .error{margin:4px 0 0}.mfa-settings .setup-otp .confirm-otp-actions button{margin-top:5px;width:15em} -/*! - * Cropper.js v1.5.12 - * https://fengyuanchen.github.io/cropperjs - * - * Copyright 2015-present Chen Fengyuan - * Released under the MIT license - * - * Date: 2021-06-12T08:00:11.623Z - */.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}.image-cropper-img-input{display:none}.image-cropper-image-container{position:relative}.image-cropper-image-container img{display:block;max-width:100%}.image-cropper-buttons-wrapper{margin-top:10px}.image-cropper-buttons-wrapper button{margin-top:5px}.profile-tab .bio{margin:0}.profile-tab .visibility-tray{padding-top:5px}.profile-tab input[type=file]{height:auto;padding:5px}.profile-tab .banner-background-preview{max-width:100%;position:relative;width:300px}.profile-tab .banner-background-preview img{width:100%}.profile-tab .uploading{font-size:1.5em;margin:.25em}.profile-tab .name-changer{width:100%}.profile-tab .current-avatar-container{height:150px;position:relative;width:150px}.profile-tab .current-avatar{border-radius:4px;border-radius:var(--avatarRadius,4px);display:block;height:100%;width:100%}.profile-tab .reset-button{background-color:rgba(0,0,0,.6);border-radius:5px;border-radius:var(--tooltipRadius,5px);cursor:pointer;font-size:1.5em;height:1.5em;line-height:1.5em;opacity:.7;position:absolute;right:.2em;text-align:center;top:.2em;width:1.5em}.profile-tab .reset-button:hover{opacity:1}.profile-tab .reset-button svg{color:#fff}.profile-tab .oauth-tokens{width:100%}.profile-tab .oauth-tokens th{text-align:left}.profile-tab .oauth-tokens .actions{text-align:right}.profile-tab-usersearch-wrapper{padding:1em}.profile-tab-bulk-actions{min-height:2em;padding:0 1em;text-align:right}.profile-tab-bulk-actions button{width:10em}.profile-tab-domain-mute-form{display:flex;flex-direction:column;padding:1em}.profile-tab-domain-mute-form button{align-self:flex-end;margin-top:1em;width:10em}.profile-tab .setting-subitem{margin-left:1.75em}.profile-tab .profile-fields{display:flex}.profile-tab .profile-fields>.emoji-input{flex:1 1 auto;margin:0 .2em .5em;min-width:0}.profile-tab .profile-fields .delete-field{align-self:center;margin:0 .2em .5em;padding:0 .5em;width:20px}.profile-tab .birthday-input{display:block;margin-bottom:1em}.css-unit-input,.css-unit-input select{margin-left:.5em;max-width:4em;min-width:4em;width:4em}.column-settings{display:flex;flex-wrap:wrap;justify-content:space-evenly}.column-settings .size-label{display:block;margin-bottom:.5em;margin-top:.5em}.color-input{display:inline-flex}.color-input-field.input{align-items:stretch;display:inline-flex;flex:0 0 0;max-width:9em;padding:.2em 8px}.color-input-field.input input{background:none;border:none;color:#b9b9ba;color:var(--inputText,#b9b9ba);margin:0;padding:0}.color-input-field.input input.textColor{flex:1 0 3em;min-width:3em;padding:0}.color-input-field.input .computedIndicator,.color-input-field.input .transparentIndicator,.color-input-field.input input.nativeColor{align-self:stretch;flex:0 0 2em;min-height:100%;min-width:2em}.color-input-field.input .transparentIndicator{background-color:#f0f;position:relative}.color-input-field.input .transparentIndicator:after,.color-input-field.input .transparentIndicator:before{background-color:#000;content:"";display:block;height:50%;position:absolute;width:50%}.color-input-field.input .transparentIndicator:after{left:0;top:0}.color-input-field.input .transparentIndicator:before{bottom:0;right:0}.color-input .label{flex:1 1 auto}.color-control input.text-input{flex:1;max-width:7em}.shadow-control{display:flex;flex-wrap:wrap;justify-content:center;margin-bottom:1em}.shadow-control .shadow-preview-container,.shadow-control .shadow-tweak{margin:5px 6px 0 0}.shadow-control .shadow-preview-container{display:flex;flex:0;flex-wrap:wrap}.shadow-control .shadow-preview-container input[type=number]{min-width:2em;width:5em}.shadow-control .shadow-preview-container .x-shift-control,.shadow-control .shadow-preview-container .y-shift-control{display:flex;flex:0}.shadow-control .shadow-preview-container .x-shift-control[disabled=disabled] *,.shadow-control .shadow-preview-container .y-shift-control[disabled=disabled] *{opacity:.5}.shadow-control .shadow-preview-container .x-shift-control{align-items:flex-start}.shadow-control .shadow-preview-container .x-shift-control .wrap,.shadow-control .shadow-preview-container input[type=range]{height:2em;margin:0;width:15em}.shadow-control .shadow-preview-container .y-shift-control{align-items:flex-end;flex-direction:column}.shadow-control .shadow-preview-container .y-shift-control .wrap{height:15em;width:2em}.shadow-control .shadow-preview-container .y-shift-control input[type=range]{transform:rotate(90deg);transform-origin:1em 1em}.shadow-control .shadow-preview-container .preview-window{align-items:center;background-color:#999;background-image:linear-gradient(45deg,#666 25%,transparent 0),linear-gradient(-45deg,#666 25%,transparent 0),linear-gradient(45deg,transparent 75%,#666 0),linear-gradient(-45deg,transparent 75%,#666 0);background-position:0 0,0 10px,10px -10px,-10px 0;background-size:20px 20px;border-radius:4px;border-radius:var(--inputRadius,4px);display:flex;flex:1;justify-content:center}.shadow-control .shadow-preview-container .preview-window .preview-block{background-color:#121a24;background-color:var(--bg,#121a24);border-radius:10px;border-radius:var(--panelRadius,10px);height:33%;width:33%}.shadow-control .shadow-tweak{flex:1;min-width:280px}.shadow-control .shadow-tweak .id-control{align-items:stretch}.shadow-control .shadow-tweak .id-control .shadow-switcher{flex:1}.shadow-control .shadow-tweak .id-control .btn,.shadow-control .shadow-tweak .id-control .shadow-switcher{margin-right:5px;min-width:1px}.shadow-control .shadow-tweak .id-control .btn{margin:0 .1em;padding:0 .4em}.font-control input.custom-font{min-width:10em}.font-control.custom .font-switcher{border-bottom-right-radius:0;border-top-right-radius:0}.font-control.custom .custom-font{border-bottom-left-radius:0;border-top-left-radius:0}.contrast-ratio{display:flex;justify-content:flex-end;margin-bottom:5px;margin-top:-4px}.contrast-ratio .label{margin-right:1em}.contrast-ratio .rating{display:inline-block;margin-left:.5em;text-align:center}.preview-container{position:relative}.underlay-preview{bottom:0;left:10px;position:absolute;right:10px;top:0}.theme-tab{padding-bottom:2em}.theme-tab .preset-switcher{margin-right:1em}.theme-tab .btn{margin-left:.25em;margin-right:.25em}.theme-tab .style-control{align-items:baseline;display:flex;margin-bottom:5px}.theme-tab .style-control .label{flex:1}.theme-tab .style-control .opt{margin:.5em}.theme-tab .style-control .color-input{flex:0 0 0}.theme-tab .style-control input,.theme-tab .style-control select{flex:0;margin:0;min-width:3em}.theme-tab .style-control input[type=number],.theme-tab .style-control select[type=number]{min-width:5em}.theme-tab .style-control input[type=range],.theme-tab .style-control select[type=range]{align-self:flex-start;flex:1;min-width:3em}.theme-tab .style-control.disabled input,.theme-tab .style-control.disabled select{opacity:.5}.theme-tab .reset-container{flex-wrap:wrap}.theme-tab .apply-container,.theme-tab .color-container,.theme-tab .fonts-container,.theme-tab .radius-container,.theme-tab .reset-container{display:flex}.theme-tab .fonts-container,.theme-tab .radius-container{flex-direction:column}.theme-tab .color-container{flex-wrap:wrap;justify-content:space-between}.theme-tab .color-container>h4{width:99%}.theme-tab .color-container,.theme-tab .fonts-container,.theme-tab .presets-container,.theme-tab .radius-container,.theme-tab .shadow-container{margin:1em 1em 0}.theme-tab .tab-header{align-items:baseline;display:flex;justify-content:space-between;margin-bottom:1em;min-height:30px;width:100%}.theme-tab .tab-header p{flex:1;margin:0 .5em 0 0}.theme-tab .tab-header-buttons{display:flex;flex-direction:column}.theme-tab .tab-header-buttons .btn{flex:0 auto;margin-bottom:.5em;min-width:1px;padding:0 1em}.theme-tab .shadow-selector .override{flex:1;margin-left:.5em}.theme-tab .shadow-selector .select-container{margin-bottom:-3px;margin-top:-4px}.theme-tab .save-load,.theme-tab .save-load-options{align-items:baseline;display:flex;flex-wrap:wrap;justify-content:center}.theme-tab .save-load .import-export,.theme-tab .save-load .presets,.theme-tab .save-load-options .import-export,.theme-tab .save-load-options .presets{margin-bottom:.5em}.theme-tab .save-load .import-export,.theme-tab .save-load-options .import-export{display:flex}.theme-tab .save-load .override,.theme-tab .save-load-options .override{margin-left:.5em}.theme-tab .save-load-options{flex-wrap:wrap;justify-content:center;margin-top:.5em}.theme-tab .save-load-options .keep-option{margin:0 .5em .5em;min-width:25%}.theme-tab .preview-container{background-color:var(--wallpaper);background-image:var(--body-background-image);background-position:50% 50%;background-size:cover;border-bottom:1px dashed #222;border-color:#222 currentcolor;border-top:1px dashed #222;border-color:var(--border,#222);margin:1em 0;padding:1em}.theme-tab .preview-container .dummy .post{display:flex;font-family:var(--postFont)}.theme-tab .preview-container .dummy .post .content{flex:1}.theme-tab .preview-container .dummy .post .content h4{margin-bottom:.25em}.theme-tab .preview-container .dummy .post .content .icons{display:flex;margin-top:.5em}.theme-tab .preview-container .dummy .post .content .icons i{margin-right:1em}.theme-tab .preview-container .dummy .after-post{align-items:center;display:flex;margin-top:1em}.theme-tab .preview-container .dummy .avatar,.theme-tab .preview-container .dummy .avatar-alt{background:linear-gradient(135deg,#b8e1fc,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd);color:#000;font-family:sans-serif;margin-right:1em;text-align:center}.theme-tab .preview-container .dummy .avatar-alt{border-radius:10px;border-radius:var(--avatarAltRadius,10px);flex:0 auto;font-size:12px;line-height:20px;margin-left:28px;min-height:20px;min-width:20px}.theme-tab .preview-container .dummy .avatar{flex:0 auto;font-size:14px;height:48px;line-height:48px;width:48px}.theme-tab .preview-container .dummy .actions{align-items:baseline;display:flex}.theme-tab .preview-container .dummy .actions .checkbox{align-items:baseline;display:inline-flex;flex:1;margin-right:1em}.theme-tab .preview-container .dummy .separator{border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);margin:1em}.theme-tab .preview-container .dummy .btn{min-width:3em}.theme-tab .radius-item{flex-basis:auto}.theme-tab .color-item,.theme-tab .radius-item{display:flex;flex:1 1 0;flex-direction:column;margin:5px 6px 0 0;min-width:20em}.theme-tab .color-item.wide,.theme-tab .radius-item.wide{min-width:60%}.theme-tab .color-item:not(.wide):nth-child(odd),.theme-tab .radius-item:not(.wide):nth-child(odd){margin-right:7px}.theme-tab .color-item .color,.theme-tab .color-item .opacity,.theme-tab .radius-item .color,.theme-tab .radius-item .opacity{align-items:baseline;display:flex}.theme-tab .theme-color-cl,.theme-tab .theme-radius-rn{align-self:stretch;background:transparent;border:0;box-shadow:none;color:var(--faint,hsla(240,1%,73%,.5))}.theme-tab .theme-color-cl,.theme-tab .theme-color-in,.theme-tab .theme-radius-in{margin-left:4px}.theme-tab .theme-radius-in{flex:1;max-width:7em;min-width:1em}.theme-tab .theme-radius-lb{max-width:50em}.theme-tab .theme-preview-content{padding:20px}.theme-tab .theme-warning{align-items:baseline;display:flex;margin-bottom:.5em}.theme-tab .theme-warning .buttons .btn{margin-bottom:.5em}.extra-content .apply-container{display:flex;flex-direction:row;flex-grow:1;justify-content:space-around}.extra-content .apply-container .btn{flex-grow:1;max-width:10em;min-height:2em;min-width:0;padding:0}.settings_tab-switcher{height:100%}.settings_tab-switcher .setting-item{border-bottom:2px solid var(--fg,#182230);margin:1em 1em 1.4em;padding-bottom:1.4em}.settings_tab-switcher .setting-item>div,.settings_tab-switcher .setting-item>label{display:block;margin-bottom:.5em}.settings_tab-switcher .setting-item>div:last-child,.settings_tab-switcher .setting-item>label:last-child{margin-bottom:0}.settings_tab-switcher .setting-item .select-multiple{display:flex}.settings_tab-switcher .setting-item .select-multiple .option-list{margin:0;padding-left:.5em}.settings_tab-switcher .setting-item:last-child{border-bottom:none;margin-bottom:1em;padding-bottom:0}.settings_tab-switcher .setting-item select{min-width:10em}.settings_tab-switcher .setting-item textarea{height:100px;max-width:100%;width:100%}.settings_tab-switcher .setting-item .unavailable,.settings_tab-switcher .setting-item .unavailable svg{color:var(--cRed,red);color:red}.settings_tab-switcher .setting-item .number-input{max-width:6em} -/*# sourceMappingURL=9114.8def3b2b7fe70b3b3712.css.map*/ \ No newline at end of file diff --git a/priv/static/static/css/9114.8def3b2b7fe70b3b3712.css.map b/priv/static/static/css/9114.8def3b2b7fe70b3b3712.css.map deleted file mode 100644 index e8f8688ae..000000000 --- a/priv/static/static/css/9114.8def3b2b7fe70b3b3712.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"static/css/9114.8def3b2b7fe70b3b3712.css","mappings":"AAEE,oBACE,gBACA,aCFF,qBACE,aCAJ,aACE,kBAEA,mBACE,cACA,WAGF,qBAME,wBCbW,CDcX,mCAGA,qBCTe,CDUf,gCACA,iBCCoB,sCDCpB,yBACA,0BACA,sCACA,8BAfA,OAGA,iBAaA,gBAjBA,kBAGA,QADA,SAgBA,UE7BJ,8BACE,gBACA,iBAEA,qCACE,WCLJ,6BACE,gBACA,iBAEA,oCACE,WCLJ,kBAIE,mBAFA,aADA,SAEA,8BAEA,wBAEA,yBACE,iBACA,gBACA,uBAGF,yBACE,WAGF,uCACE,iBCfF,4BAEE,mBADA,YACA,CAEA,8BACE,YAIJ,qCAKE,qDAAuD,CACvD,yDAA2D,CAC3D,6DAA+D,CAC/D,8CAA+C,CAP/C,wBJJgB,CIKhB,6CACA,qCAKgD,CAGlD,wBAEE,mBAIA,oEALA,aAEA,cAGA,CAEA,gCACE,OAIJ,kCAEE,UADA,cACA,CCtCF,2BACE,aACA,kBAEA,kCACE,eCNN,sBACE,YAEA,0CACE,YAGF,oCAGE,eADA,cADA,gBAEA,CAGF,0CACE,WAGF,wCAEE,aACA,sBAFA,WAEA,CAGF,0CACE,oBACA,eACA,WCzBJ,mBACE,qBACA,kBAGF,kBACE,gBACA,eACA,kBCRF,qBACE,qBACA,kBAGF,oBACE,gBACA,eACA,kBCLA,2BACE,YTWgB,CSVhB,4BAGF,gCACE,0CCNF,sDAKE,qBAHA,aACA,eACA,6BACA,CAGF,uBACE,YVGgB,CUFhB,4BAGF,yBACE,aAEA,eADA,sBACA,CAEA,kCACE,OACA,mBAEF,wCACA,+CAGE,qDAEE,eADA,UACA;AChCR;;;;;;;;EAQE,CAEF,mBACE,aAAc,CACd,WAAY,CACZ,aAAc,CACd,iBAAkB,CAClB,qBAAsB,CACtB,iBAAkB,CAClB,wBAAyB,CACzB,qBAAsB,CACtB,oBAAqB,CACrB,gBACF,CAEA,uBACE,aAAc,CACd,WAAY,CACZ,sBAAuB,CACvB,yBAA2B,CAC3B,wBAA0B,CAC1B,sBAAwB,CACxB,qBAAuB,CACvB,UACF,CAEA,qFAKE,QAAS,CACT,MAAO,CACP,iBAAkB,CAClB,OAAQ,CACR,KACF,CAEA,kCAEE,eACF,CAEA,kBACE,qBAAsB,CACtB,SACF,CAEA,eACE,qBAAsB,CACtB,UACF,CAEA,kBACE,aAAc,CACd,WAAY,CACZ,sBAAuB,CACvB,kCAAuC,CACvC,eAAgB,CAChB,UACF,CAEA,gBACE,oBAAqB,CACrB,aAAc,CACd,UAAY,CACZ,iBACF,CAEA,yBACE,uBAAwB,CACxB,oBAAqB,CACrB,gBAAsB,CACtB,MAAO,CACP,aAAmB,CACnB,UACF,CAEA,yBACE,qBAAsB,CACtB,sBAAuB,CACvB,WAAY,CACZ,cAAoB,CACpB,KAAM,CACN,eACF,CAEA,gBACE,aAAc,CACd,QAAS,CACT,QAAS,CACT,WAAa,CACb,iBAAkB,CAClB,OAAQ,CACR,OACF,CAEA,6CAEE,qBAAsB,CACtB,WAAY,CACZ,aAAc,CACd,iBACF,CAEA,uBACE,UAAW,CACX,SAAU,CACV,KAAM,CACN,SACF,CAEA,sBACE,UAAW,CACX,MAAO,CACP,QAAS,CACT,SACF,CAEA,2CAGE,aAAc,CACd,WAAY,CACZ,UAAY,CACZ,iBAAkB,CAClB,UACF,CAEA,cACE,qBAAsB,CACtB,MAAO,CACP,KACF,CAEA,cACE,qBACF,CAEA,qBACE,gBAAiB,CACjB,UAAW,CACX,KAAM,CACN,SACF,CAEA,qBACE,gBAAiB,CACjB,UAAW,CACX,MAAO,CACP,QACF,CAEA,qBACE,gBAAiB,CACjB,SAAU,CACV,KAAM,CACN,SACF,CAEA,qBACE,WAAY,CACZ,gBAAiB,CACjB,UAAW,CACX,MACF,CAEA,eACE,qBAAsB,CACtB,UAAW,CACX,WAAa,CACb,SACF,CAEA,uBACE,gBAAiB,CACjB,eAAgB,CAChB,UAAW,CACX,OACF,CAEA,uBACE,gBAAiB,CACjB,QAAS,CACT,gBAAiB,CACjB,QACF,CAEA,uBACE,gBAAiB,CACjB,SAAU,CACV,eAAgB,CAChB,OACF,CAEA,uBACE,WAAY,CACZ,eAAgB,CAChB,QAAS,CACT,gBACF,CAEA,wBACE,kBAAmB,CACnB,UAAW,CACX,QACF,CAEA,wBACE,kBAAmB,CACnB,SAAU,CACV,QACF,CAEA,wBACE,WAAY,CACZ,kBAAmB,CACnB,SACF,CAEA,wBACE,WAAY,CACZ,kBAAmB,CACnB,WAAY,CACZ,SAAU,CACV,UAAW,CACX,UACF,CAEA,yBACE,wBACE,WAAY,CACZ,UACF,CACF,CAEA,yBACE,wBACE,WAAY,CACZ,UACF,CACF,CAEA,0BACE,wBACE,UAAW,CACX,WAAa,CACb,SACF,CACF,CAEA,+BACE,qBAAsB,CACtB,WAAY,CACZ,WAAY,CACZ,aAAc,CACd,WAAY,CACZ,SAAU,CACV,iBAAkB,CAClB,UAAW,CACX,UACF,CAEA,mBACE,SACF,CAEA,YACE,4QACF,CAEA,cACE,aAAc,CACd,QAAS,CACT,iBAAkB,CAClB,OACF,CAEA,gBACE,sBACF,CAEA,cACE,WACF,CAEA,cACE,gBACF,CAEA,qIAIE,kBACF,CC7SE,yBACE,aAGF,+BACE,kBAEA,mCACE,cACA,eAIJ,+BACE,gBAEA,sCACE,eChBJ,kBACE,SAGF,8BACE,gBAGF,8BAEE,YADA,WACA,CAGF,wCACE,eAEA,kBADA,WACA,CAEA,4CACE,WAIJ,wBACE,gBACA,aAGF,2BACE,WAGF,uCAGE,aAFA,kBACA,WACA,CAGF,6BAIE,iBbnBqB,CaoBrB,sCAJA,cAEA,YADA,UAGA,CAGF,2BAME,gCAFA,iBb5BsB,Ca6BtB,uCAQA,eADA,gBAHA,aAEA,kBAJA,WANA,kBAEA,WAOA,kBARA,SAMA,WAKA,CAEA,iCACE,UAGF,+BACE,WAIJ,2BACE,WAEA,8BACE,gBAGF,oCACE,iBAIJ,gCACE,YAGF,0BAGE,eADA,cADA,gBAEA,CAEA,iCACE,WAIJ,8BAEE,aACA,sBAFA,WAEA,CAEA,qCACE,oBACA,eACA,WAIJ,8BACE,mBAGF,6BACE,aAEA,0CACE,cACA,mBACA,YAGF,2CAEE,kBACA,mBACA,eAHA,UAGA,CAIJ,6BACE,cACA,kBCrIJ,uCAEE,iBAEA,cACA,cAFA,SAEA,CCLF,iBACE,aAEA,eADA,4BACA,CAGF,6BACE,cACA,mBACA,gBCRF,aACE,oBAEA,yBAIE,oBAHA,oBACA,WACA,cAEA,iBAEA,+BACE,gBAGA,YAFA,ahBHgB,CgBIhB,+BAGA,QAAO,CADP,SACA,CAEA,yCACE,aACA,cACA,UAWJ,sIAIE,mBAFA,aAGA,gBAFA,aAEA,CAGF,+CAEE,sBACA,kBAEA,2GAIE,sBADA,WADA,cAIA,WADA,kBAEA,UAGF,qDAEE,MAAK,CADL,KACA,CAGF,sDACE,SACA,QAKN,oBACE,cCpEF,gCAEE,MAAK,CADL,aACA,CCDJ,gBACE,aACA,eACA,uBACA,kBAEA,wEAEE,mBAGF,0CAEE,aADA,OAEA,eAIA,6DAEE,cADA,SACA,CAGF,sHAEE,aACA,OAEA,gKACE,WAIJ,2DACE,uBAGF,6HAIE,WAFA,SACA,UACA,CAGF,2DAEE,qBADA,qBACA,CAEA,iEAEE,YADA,SAjCG,CAqCL,6EAEE,wBADA,wBACA,CAIJ,0DAIE,mBAFA,sBAIA,0MACE,CAKF,kDADA,0BAEA,iBlBnDkB,CkBoDlB,qCAXA,aAFA,OAIA,sBASA,CAEA,yEAGE,wBlB7EO,CkB8EP,mCACA,kBlB9DgB,CkB+DhB,sCAJA,WADA,SAKA,CAKN,8BACE,OACA,gBAEA,0CACE,oBAEA,2DACE,OAGF,0GAGE,iBADA,aACA,CAGF,+CAEE,cADA,cACA,CCxGN,gCACE,eAKA,oCAEE,4BAA2B,CAD3B,yBACA,CAGF,kCAEE,2BAA0B,CAD1B,wBACA,CChBN,gBACE,aACA,yBAEA,kBADA,eACA,CAEA,uBACE,iBAGF,wBACE,qBAEA,iBADA,iBACA,CCbJ,mBACE,kBAGF,kBAGE,SACA,UAHA,kBAIA,WAHA,KAGA,CCRF,WACE,mBAEA,4BACE,iBAGF,gBACE,kBACA,mBAGF,0BAEE,qBADA,aAEA,kBAEA,iCACE,OAGF,+BACE,YAGF,uCACE,WAGF,iEAIE,MAAK,CADL,SADA,aAEA,CAEA,2FACE,cAGF,yFAGE,sBAFA,OACA,aACA,CAKF,mFAEE,WAKN,4BACE,eAGF,6IAKE,aAGF,yDAEE,sBAGF,4BAKE,eACA,8BALA,+BACE,UAOJ,gJAKE,iBAGF,uBAGE,qBAFA,aACA,8BAIA,kBADA,gBADA,UAEA,CAEA,yBACE,OAEA,kBAIJ,+BACE,aACA,sBAEA,oCAEE,YAEA,mBAHA,cAEA,aACA,CAKF,sCACE,OACA,iBAGF,8CAEE,mBADA,eACA,CAIJ,oDAIE,qBAFA,aAGA,eAFA,sBAEA,CAEA,wJAEE,mBAGF,kFACE,aAGF,wEACE,iBAIJ,8BACE,eAEA,uBADA,eACA,CAEA,2CACE,mBACA,cAIJ,8BAOE,kCACA,8CAEA,4BADA,sBANA,6BtBxJe,CsBwJf,8BtBxJe,CsBwJf,0BtBxJe,CsByJf,gCACA,aACA,WAIA,CAGE,2CAEE,aADA,2BACA,CAEA,oDACE,OAEA,uDACE,oBAGF,2DAEE,aADA,eACA,CAEA,6DACE,iBAMR,iDAGE,mBADA,aADA,cAEA,CAGF,8FAEE,0HACE,CAWF,WACA,uBAEA,iBADA,iBACA,CAGF,iDAOE,kBtB1MoB,CsB2MpB,0CAPA,YAEA,eAGA,iBAJA,iBAGA,gBADA,cAIA,CAGF,6CACE,YAGA,eADA,YAEA,iBAHA,UAGA,CAGF,8CAEE,qBADA,YACA,CAEA,wDAEE,qBADA,oBAGA,MAAK,CADL,gBACA,CAIJ,gDAGE,uBtBpPW,CsBoPX,iBtBpPW,CsBqPX,gCAHA,UAGA,CAGF,0CACE,cAKN,wBACE,gBAGF,+CAIE,aAEA,WADA,sBAFA,mBADA,cAIA,CAEA,yDACE,cAGF,mGACE,iBAGF,8HAGE,qBADA,YACA,CAIJ,uDAME,mBAFA,uBAFA,SACA,gBAEA,sCACA,CAGF,kFAGE,gBAGF,4BAGE,MAAK,CADL,cADA,aAEA,CAGF,4BACE,eAGF,kCACE,aAGF,0BAEE,qBADA,aAEA,mBAGE,wCACE,mBAON,gCACE,aACA,mBAEA,WAAU,CADV,4BACA,CAGA,qCACE,YAGA,eAFA,eACA,YAEA,UC1VN,uBACE,YAEA,qCACE,0CACA,qBACA,qBAEA,oFAEE,cACA,mBAEA,0GACE,gBAIJ,sDACE,aAEA,mEACE,SACA,kBAIJ,gDACE,mBAEA,kBADA,gBACA,CAGF,4CACE,eAGF,8CAGE,aADA,eADA,UAEA,CAGF,wGAEE,sBACA,SvBnCW,CuBsCb,mDACE","sources":["webpack://pleroma_fe/./src/components/importer/importer.vue","webpack://pleroma_fe/./src/components/exporter/exporter.vue","webpack://pleroma_fe/./src/components/autosuggest/autosuggest.vue","webpack://pleroma_fe/./src/_variables.scss","webpack://pleroma_fe/./src/components/block_card/block_card.vue","webpack://pleroma_fe/./src/components/mute_card/mute_card.vue","webpack://pleroma_fe/./src/components/domain_mute_card/domain_mute_card.vue","webpack://pleroma_fe/./src/components/selectable_list/selectable_list.vue","webpack://pleroma_fe/./src/hocs/with_subscription/with_subscription.scss","webpack://pleroma_fe/./src/components/settings_modal/tabs/mutes_and_blocks_tab.scss","webpack://pleroma_fe/./src/components/settings_modal/helpers/modified_indicator.vue","webpack://pleroma_fe/./src/components/settings_modal/helpers/server_side_indicator.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/security_tab/mfa.vue","webpack://pleroma_fe/./node_modules/cropperjs/dist/cropper.css","webpack://pleroma_fe/./src/components/image_cropper/image_cropper.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/profile_tab.scss","webpack://pleroma_fe/./src/components/settings_modal/helpers/size_setting.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/general_tab.vue","webpack://pleroma_fe/./src/components/color_input/color_input.scss","webpack://pleroma_fe/./src/components/color_input/color_input.vue","webpack://pleroma_fe/./src/components/shadow_control/shadow_control.vue","webpack://pleroma_fe/./src/components/font_control/font_control.vue","webpack://pleroma_fe/./src/components/contrast_ratio/contrast_ratio.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/theme_tab/preview.vue","webpack://pleroma_fe/./src/components/settings_modal/tabs/theme_tab/theme_tab.scss","webpack://pleroma_fe/./src/components/settings_modal/settings_modal_content.scss"],"sourcesContent":["\n.importer {\n &-uploading {\n font-size: 1.5em;\n margin: 0.25em;\n }\n}\n","\n.exporter {\n &-processing {\n margin: 0.25em;\n }\n}\n","\n@import \"../../variables\";\n\n.autosuggest {\n position: relative;\n\n &-input {\n display: block;\n width: 100%;\n }\n\n &-results {\n position: absolute;\n left: 0;\n top: 100%;\n right: 0;\n max-height: 400px;\n background-color: $fallback--bg;\n background-color: var(--bg, $fallback--bg);\n border-style: solid;\n border-width: 1px;\n border-color: $fallback--border;\n border-color: var(--border, $fallback--border);\n border-radius: $fallback--inputRadius;\n border-radius: var(--inputRadius, $fallback--inputRadius);\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);\n box-shadow: var(--panelShadow);\n overflow-y: auto;\n z-index: 1;\n }\n}\n","$main-color: #f58d2c;\n$main-background: white;\n$darkened-background: whitesmoke;\n\n$fallback--bg: #121a24;\n$fallback--fg: #182230;\n$fallback--faint: rgb(185 185 186 / 50%);\n$fallback--text: #b9b9ba;\n$fallback--link: #d8a070;\n$fallback--icon: #666;\n$fallback--lightBg: rgb(21 30 42);\n$fallback--lightText: #b9b9ba;\n$fallback--border: #222;\n$fallback--cRed: #f00;\n$fallback--cBlue: #0095ff;\n$fallback--cGreen: #0fa00f;\n$fallback--cOrange: orange;\n\n$fallback--alertError: rgb(211 16 20 / 50%);\n$fallback--alertWarning: rgb(111 111 20 / 50%);\n\n$fallback--panelRadius: 10px;\n$fallback--checkboxRadius: 2px;\n$fallback--btnRadius: 4px;\n$fallback--inputRadius: 4px;\n$fallback--tooltipRadius: 5px;\n$fallback--avatarRadius: 4px;\n$fallback--avatarAltRadius: 10px;\n$fallback--attachmentRadius: 10px;\n$fallback--chatMessageRadius: 10px;\n\n$fallback--buttonShadow: 0 0 2px 0 rgb(0 0 0 / 100%),\n 0 1px 0 0 rgb(255 255 255 / 20%) inset,\n 0 -1px 0 0 rgb(0 0 0 / 20%) inset;\n\n$status-margin: 0.75em;\n","\n.block-card-content-container {\n margin-top: 0.5em;\n text-align: right;\n\n button {\n width: 10em;\n }\n}\n","\n.mute-card-content-container {\n margin-top: 0.5em;\n text-align: right;\n\n button {\n width: 10em;\n }\n}\n","\n.domain-mute-card {\n flex: 1 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0.6em 1em 0.6em 0;\n\n &-domain {\n margin-right: 1em;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n button {\n width: 10em;\n }\n\n .autosuggest-results & {\n padding-left: 1em;\n }\n}\n","\n@import \"../../variables\";\n\n.selectable-list {\n &-item-inner {\n display: flex;\n align-items: center;\n\n > * {\n min-width: 0;\n }\n }\n\n &-item-selected-inner {\n background-color: $fallback--lightBg;\n background-color: var(--selectedMenu, $fallback--lightBg);\n color: var(--selectedMenuText, $fallback--text);\n\n --faint: var(--selectedMenuFaintText, $fallback--faint);\n --faintLink: var(--selectedMenuFaintLink, $fallback--faint);\n --lightText: var(--selectedMenuLightText, $fallback--lightText);\n --icon: var(--selectedMenuIcon, $fallback--icon);\n }\n\n &-header {\n display: flex;\n align-items: center;\n padding: 0.6em 0;\n border-bottom: 2px solid;\n border-bottom-color: $fallback--border;\n border-bottom-color: var(--border, $fallback--border);\n\n &-actions {\n flex: 1;\n }\n }\n\n &-checkbox-wrapper {\n padding: 0 10px;\n flex: none;\n }\n}\n",".with-subscription {\n &-loading {\n padding: 10px;\n text-align: center;\n\n .error {\n font-size: 1rem;\n }\n }\n}\n",".mutes-and-blocks-tab {\n height: 100%;\n\n .usersearch-wrapper {\n padding: 1em;\n }\n\n .bulk-actions {\n text-align: right;\n padding: 0 1em;\n min-height: 2em;\n }\n\n .bulk-action-button {\n width: 10em;\n }\n\n .domain-mute-form {\n padding: 1em;\n display: flex;\n flex-direction: column;\n }\n\n .domain-mute-button {\n align-self: flex-end;\n margin-top: 1em;\n width: 10em;\n }\n}\n","\n.ModifiedIndicator {\n display: inline-block;\n position: relative;\n}\n\n.modified-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n.ServerSideIndicator {\n display: inline-block;\n position: relative;\n}\n\n.serverside-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n@import \"../../../../variables\";\n\n.mfa-backup-codes {\n .warning {\n color: $fallback--cOrange;\n color: var(--cOrange, $fallback--cOrange);\n }\n\n .backup-codes {\n font-family: var(--postCodeFont, monospace);\n }\n}\n","\n@import \"../../../../variables\";\n\n.mfa-settings {\n .mfa-heading,\n .method-item {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: baseline;\n }\n\n .warning {\n color: $fallback--cOrange;\n color: var(--cOrange, $fallback--cOrange);\n }\n\n .setup-otp {\n display: flex;\n justify-content: center;\n flex-wrap: wrap;\n\n .qr-code {\n flex: 1;\n padding-right: 10px;\n }\n .verify { flex: 1; }\n .error { margin: 4px 0 0; }\n\n .confirm-otp-actions {\n button {\n width: 15em;\n margin-top: 5px;\n }\n }\n }\n}\n","/*!\n * Cropper.js v1.5.12\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2021-06-12T08:00:11.623Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: 0.5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline: 1px solid #39f;\n outline-color: rgba(51, 153, 255, 0.75);\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: 0.5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n}\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n}\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: 0.75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center::before,\n.cropper-center::after {\n background-color: #eee;\n content: ' ';\n display: block;\n position: absolute;\n}\n\n.cropper-center::before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n}\n\n.cropper-center::after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: 0.1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n}\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n}\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: 0.75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n}\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n}\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n}\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n}\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n}\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n}\n\n@media (min-width: 768px) {\n .cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n}\n\n@media (min-width: 992px) {\n .cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n}\n\n@media (min-width: 1200px) {\n .cropper-point.point-se {\n height: 5px;\n opacity: 0.75;\n width: 5px;\n }\n}\n\n.cropper-point.point-se::before {\n background-color: #39f;\n bottom: -50%;\n content: ' ';\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n}\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n","\n.image-cropper {\n &-img-input {\n display: none;\n }\n\n &-image-container {\n position: relative;\n\n img {\n display: block;\n max-width: 100%;\n }\n }\n\n &-buttons-wrapper {\n margin-top: 10px;\n\n button {\n margin-top: 5px;\n }\n }\n}\n","@import \"../../../variables\";\n\n.profile-tab {\n .bio {\n margin: 0;\n }\n\n .visibility-tray {\n padding-top: 5px;\n }\n\n input[type=\"file\"] {\n padding: 5px;\n height: auto;\n }\n\n .banner-background-preview {\n max-width: 100%;\n width: 300px;\n position: relative;\n\n img {\n width: 100%;\n }\n }\n\n .uploading {\n font-size: 1.5em;\n margin: 0.25em;\n }\n\n .name-changer {\n width: 100%;\n }\n\n .current-avatar-container {\n position: relative;\n width: 150px;\n height: 150px;\n }\n\n .current-avatar {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: $fallback--avatarRadius;\n border-radius: var(--avatarRadius, $fallback--avatarRadius);\n }\n\n .reset-button {\n position: absolute;\n top: 0.2em;\n right: 0.2em;\n border-radius: $fallback--tooltipRadius;\n border-radius: var(--tooltipRadius, $fallback--tooltipRadius);\n background-color: rgb(0 0 0 / 60%);\n opacity: 0.7;\n width: 1.5em;\n height: 1.5em;\n text-align: center;\n line-height: 1.5em;\n font-size: 1.5em;\n cursor: pointer;\n\n &:hover {\n opacity: 1;\n }\n\n svg {\n color: white;\n }\n }\n\n .oauth-tokens {\n width: 100%;\n\n th {\n text-align: left;\n }\n\n .actions {\n text-align: right;\n }\n }\n\n &-usersearch-wrapper {\n padding: 1em;\n }\n\n &-bulk-actions {\n text-align: right;\n padding: 0 1em;\n min-height: 2em;\n\n button {\n width: 10em;\n }\n }\n\n &-domain-mute-form {\n padding: 1em;\n display: flex;\n flex-direction: column;\n\n button {\n align-self: flex-end;\n margin-top: 1em;\n width: 10em;\n }\n }\n\n .setting-subitem {\n margin-left: 1.75em;\n }\n\n .profile-fields {\n display: flex;\n\n & > .emoji-input {\n flex: 1 1 auto;\n margin: 0 0.2em 0.5em;\n min-width: 0;\n }\n\n .delete-field {\n width: 20px;\n align-self: center;\n margin: 0 0.2em 0.5em;\n padding: 0 0.5em;\n }\n }\n\n .birthday-input {\n display: block;\n margin-bottom: 1em;\n }\n}\n","\n.css-unit-input,\n.css-unit-input select {\n margin-left: 0.5em;\n width: 4em;\n max-width: 4em;\n min-width: 4em;\n}\n","\n.column-settings {\n display: flex;\n justify-content: space-evenly;\n flex-wrap: wrap;\n}\n\n.column-settings .size-label {\n display: block;\n margin-bottom: 0.5em;\n margin-top: 0.5em;\n}\n","@import \"../../variables\";\n\n.color-input {\n display: inline-flex;\n\n &-field.input {\n display: inline-flex;\n flex: 0 0 0;\n max-width: 9em;\n align-items: stretch;\n padding: 0.2em 8px;\n\n input {\n background: none;\n color: $fallback--lightText;\n color: var(--inputText, $fallback--lightText);\n border: none;\n padding: 0;\n margin: 0;\n\n &.textColor {\n flex: 1 0 3em;\n min-width: 3em;\n padding: 0;\n }\n\n &.nativeColor {\n flex: 0 0 2em;\n min-width: 2em;\n align-self: stretch;\n min-height: 100%;\n }\n }\n\n .computedIndicator,\n .transparentIndicator {\n flex: 0 0 2em;\n min-width: 2em;\n align-self: stretch;\n min-height: 100%;\n }\n\n .transparentIndicator {\n // forgot to install counter-strike source, ooops\n background-color: #f0f;\n position: relative;\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n background-color: #000;\n position: absolute;\n height: 50%;\n width: 50%;\n }\n\n &::after {\n top: 0;\n left: 0;\n }\n\n &::before {\n bottom: 0;\n right: 0;\n }\n }\n }\n\n .label {\n flex: 1 1 auto;\n }\n}\n","\n.color-control {\n input.text-input {\n max-width: 7em;\n flex: 1;\n }\n}\n","\n@import \"../../variables\";\n\n.shadow-control {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n margin-bottom: 1em;\n\n .shadow-preview-container,\n .shadow-tweak {\n margin: 5px 6px 0 0;\n }\n\n .shadow-preview-container {\n flex: 0;\n display: flex;\n flex-wrap: wrap;\n\n $side: 15em;\n\n input[type=\"number\"] {\n width: 5em;\n min-width: 2em;\n }\n\n .x-shift-control,\n .y-shift-control {\n display: flex;\n flex: 0;\n\n &[disabled=\"disabled\"] * {\n opacity: 0.5;\n }\n }\n\n .x-shift-control {\n align-items: flex-start;\n }\n\n .x-shift-control .wrap,\n input[type=\"range\"] {\n margin: 0;\n width: $side;\n height: 2em;\n }\n\n .y-shift-control {\n flex-direction: column;\n align-items: flex-end;\n\n .wrap {\n width: 2em;\n height: $side;\n }\n\n input[type=\"range\"] {\n transform-origin: 1em 1em;\n transform: rotate(90deg);\n }\n }\n\n .preview-window {\n flex: 1;\n background-color: #999;\n display: flex;\n align-items: center;\n justify-content: center;\n background-image:\n linear-gradient(45deg, #666 25%, transparent 25%),\n linear-gradient(-45deg, #666 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #666 75%),\n linear-gradient(-45deg, transparent 75%, #666 75%);\n background-size: 20px 20px;\n background-position: 0 0, 0 10px, 10px -10px, -10px 0;\n border-radius: $fallback--inputRadius;\n border-radius: var(--inputRadius, $fallback--inputRadius);\n\n .preview-block {\n width: 33%;\n height: 33%;\n background-color: $fallback--bg;\n background-color: var(--bg, $fallback--bg);\n border-radius: $fallback--panelRadius;\n border-radius: var(--panelRadius, $fallback--panelRadius);\n }\n }\n }\n\n .shadow-tweak {\n flex: 1;\n min-width: 280px;\n\n .id-control {\n align-items: stretch;\n\n .shadow-switcher {\n flex: 1;\n }\n\n .shadow-switcher,\n .btn {\n min-width: 1px;\n margin-right: 5px;\n }\n\n .btn {\n padding: 0 0.4em;\n margin: 0 0.1em;\n }\n }\n }\n}\n","\n@import \"../../variables\";\n\n.font-control {\n input.custom-font {\n min-width: 10em;\n }\n\n &.custom {\n /* TODO Should make proper joiners... */\n .font-switcher {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n\n .custom-font {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n }\n}\n","\n.contrast-ratio {\n display: flex;\n justify-content: flex-end;\n margin-top: -4px;\n margin-bottom: 5px;\n\n .label {\n margin-right: 1em;\n }\n\n .rating {\n display: inline-block;\n text-align: center;\n margin-left: 0.5em;\n }\n}\n","\n.preview-container {\n position: relative;\n}\n\n.underlay-preview {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 10px;\n right: 10px;\n}\n","@import \"src/variables\";\n\n.theme-tab {\n padding-bottom: 2em;\n\n .preset-switcher {\n margin-right: 1em;\n }\n\n .btn {\n margin-left: 0.25em;\n margin-right: 0.25em;\n }\n\n .style-control {\n display: flex;\n align-items: baseline;\n margin-bottom: 5px;\n\n .label {\n flex: 1;\n }\n\n .opt {\n margin: 0.5em;\n }\n\n .color-input {\n flex: 0 0 0;\n }\n\n input,\n select {\n min-width: 3em;\n margin: 0;\n flex: 0;\n\n &[type=\"number\"] {\n min-width: 5em;\n }\n\n &[type=\"range\"] {\n flex: 1;\n min-width: 3em;\n align-self: flex-start;\n }\n }\n\n &.disabled {\n input,\n select {\n opacity: 0.5;\n }\n }\n }\n\n .reset-container {\n flex-wrap: wrap;\n }\n\n .fonts-container,\n .reset-container,\n .apply-container,\n .radius-container,\n .color-container, {\n display: flex;\n }\n\n .fonts-container,\n .radius-container {\n flex-direction: column;\n }\n\n .color-container {\n > h4 {\n width: 99%;\n }\n\n flex-wrap: wrap;\n justify-content: space-between;\n }\n\n .fonts-container,\n .color-container,\n .shadow-container,\n .radius-container,\n .presets-container {\n margin: 1em 1em 0;\n }\n\n .tab-header {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n width: 100%;\n min-height: 30px;\n margin-bottom: 1em;\n\n p {\n flex: 1;\n margin: 0;\n margin-right: 0.5em;\n }\n }\n\n .tab-header-buttons {\n display: flex;\n flex-direction: column;\n\n .btn {\n min-width: 1px;\n flex: 0 auto;\n padding: 0 1em;\n margin-bottom: 0.5em;\n }\n }\n\n .shadow-selector {\n .override {\n flex: 1;\n margin-left: 0.5em;\n }\n\n .select-container {\n margin-top: -4px;\n margin-bottom: -3px;\n }\n }\n\n .save-load,\n .save-load-options {\n display: flex;\n justify-content: center;\n align-items: baseline;\n flex-wrap: wrap;\n\n .presets,\n .import-export {\n margin-bottom: 0.5em;\n }\n\n .import-export {\n display: flex;\n }\n\n .override {\n margin-left: 0.5em;\n }\n }\n\n .save-load-options {\n flex-wrap: wrap;\n margin-top: 0.5em;\n justify-content: center;\n\n .keep-option {\n margin: 0 0.5em 0.5em;\n min-width: 25%;\n }\n }\n\n .preview-container {\n border-top: 1px dashed;\n border-bottom: 1px dashed;\n border-color: $fallback--border;\n border-color: var(--border, $fallback--border);\n margin: 1em 0;\n padding: 1em;\n background-color: var(--wallpaper);\n background-image: var(--body-background-image);\n background-size: cover;\n background-position: 50% 50%;\n\n .dummy {\n .post {\n font-family: var(--postFont);\n display: flex;\n\n .content {\n flex: 1;\n\n h4 {\n margin-bottom: 0.25em;\n }\n\n .icons {\n margin-top: 0.5em;\n display: flex;\n\n i {\n margin-right: 1em;\n }\n }\n }\n }\n\n .after-post {\n margin-top: 1em;\n display: flex;\n align-items: center;\n }\n\n .avatar,\n .avatar-alt {\n background:\n linear-gradient(\n 135deg,\n #b8e1fc 0%,\n #a9d2f3 10%,\n #90bae4 25%,\n #90bcea 37%,\n #90bff0 50%,\n #6ba8e5 51%,\n #a2daf5 83%,\n #bdf3fd 100%\n );\n color: black;\n font-family: sans-serif;\n text-align: center;\n margin-right: 1em;\n }\n\n .avatar-alt {\n flex: 0 auto;\n margin-left: 28px;\n font-size: 12px;\n min-width: 20px;\n min-height: 20px;\n line-height: 20px;\n border-radius: $fallback--avatarAltRadius;\n border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);\n }\n\n .avatar {\n flex: 0 auto;\n width: 48px;\n height: 48px;\n font-size: 14px;\n line-height: 48px;\n }\n\n .actions {\n display: flex;\n align-items: baseline;\n\n .checkbox {\n display: inline-flex;\n align-items: baseline;\n margin-right: 1em;\n flex: 1;\n }\n }\n\n .separator {\n margin: 1em;\n border-bottom: 1px solid;\n border-color: $fallback--border;\n border-color: var(--border, $fallback--border);\n }\n\n .btn {\n min-width: 3em;\n }\n }\n }\n\n .radius-item {\n flex-basis: auto;\n }\n\n .radius-item,\n .color-item {\n min-width: 20em;\n margin: 5px 6px 0 0;\n display: flex;\n flex-direction: column;\n flex: 1 1 0;\n\n &.wide {\n min-width: 60%;\n }\n\n &:not(.wide):nth-child(2n+1) {\n margin-right: 7px;\n }\n\n .color,\n .opacity {\n display: flex;\n align-items: baseline;\n }\n }\n\n .theme-radius-rn,\n .theme-color-cl {\n border: 0;\n box-shadow: none;\n background: transparent;\n color: var(--faint, $fallback--faint);\n align-self: stretch;\n }\n\n .theme-color-cl,\n .theme-radius-in,\n .theme-color-in {\n margin-left: 4px;\n }\n\n .theme-radius-in {\n min-width: 1em;\n max-width: 7em;\n flex: 1;\n }\n\n .theme-radius-lb {\n max-width: 50em;\n }\n\n .theme-preview-content {\n padding: 20px;\n }\n\n .theme-warning {\n display: flex;\n align-items: baseline;\n margin-bottom: 0.5em;\n\n .buttons {\n .btn {\n margin-bottom: 0.5em;\n }\n }\n }\n}\n\n.extra-content {\n .apply-container {\n display: flex;\n flex-direction: row;\n justify-content: space-around;\n flex-grow: 1;\n\n /* stylelint-disable-next-line no-descending-specificity */\n .btn {\n flex-grow: 1;\n min-height: 2em;\n min-width: 0;\n max-width: 10em;\n padding: 0;\n }\n }\n}\n","@import \"src/variables\";\n\n.settings_tab-switcher {\n height: 100%;\n\n .setting-item {\n border-bottom: 2px solid var(--fg, $fallback--fg);\n margin: 1em 1em 1.4em;\n padding-bottom: 1.4em;\n\n > div,\n > label {\n display: block;\n margin-bottom: 0.5em;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n .select-multiple {\n display: flex;\n\n .option-list {\n margin: 0;\n padding-left: 0.5em;\n }\n }\n\n &:last-child {\n border-bottom: none;\n padding-bottom: 0;\n margin-bottom: 1em;\n }\n\n select {\n min-width: 10em;\n }\n\n textarea {\n width: 100%;\n max-width: 100%;\n height: 100px;\n }\n\n .unavailable,\n .unavailable svg {\n color: var(--cRed, $fallback--cRed);\n color: $fallback--cRed;\n }\n\n .number-input {\n max-width: 6em;\n }\n }\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/9801.cfe503d4c949ae0c3813.css b/priv/static/static/css/9801.cfe503d4c949ae0c3813.css new file mode 100644 index 000000000..b27df4a19 --- /dev/null +++ b/priv/static/static/css/9801.cfe503d4c949ae0c3813.css @@ -0,0 +1,2 @@ +.ModifiedIndicator{display:inline-block;position:relative}.modified-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.ProfileSettingIndicator{display:inline-block;position:relative}.profilesetting-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.DraftButtons{display:inline-block;position:relative}.DraftButtons .button-default{margin-left:.5em}.draft-tooltip{margin:.5em 1em;min-width:10em;text-align:center}.AttachmentSetting .attachment{display:block;height:15em;margin-bottom:.5em;width:100%}.AttachmentSetting .attachment-input{display:flex;flex-direction:column;margin-left:1em;width:20em}.AttachmentSetting .controls{margin-bottom:.5em}.AttachmentSetting .controls button,.AttachmentSetting .controls input{width:100%}.frontends-tab .cards-list{padding:0}.frontends-tab dd{word-wrap:nowrap;max-width:10em;overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.settings_tab-switcher{height:100%}.settings_tab-switcher .setting-item{border-bottom:2px solid var(--fg,#182230);margin:1em 1em 1.4em;padding-bottom:1.4em}.settings_tab-switcher .setting-item>div,.settings_tab-switcher .setting-item>label{display:block;margin-bottom:.5em}.settings_tab-switcher .setting-item>div:last-child,.settings_tab-switcher .setting-item>label:last-child{margin-bottom:0}.settings_tab-switcher .setting-item .select-multiple{display:flex}.settings_tab-switcher .setting-item .select-multiple .option-list{margin:0;padding-left:.5em}.settings_tab-switcher .setting-item:last-child{border-bottom:none;margin-bottom:1em;padding-bottom:0}.settings_tab-switcher .setting-item select{min-width:10em}.settings_tab-switcher .setting-item textarea{height:100px;max-width:100%;width:100%}.settings_tab-switcher .setting-item .unavailable,.settings_tab-switcher .setting-item .unavailable svg{color:var(--cRed,red);color:red} +/*# sourceMappingURL=9801.cfe503d4c949ae0c3813.css.map*/ \ No newline at end of file diff --git a/priv/static/static/css/9801.cfe503d4c949ae0c3813.css.map b/priv/static/static/css/9801.cfe503d4c949ae0c3813.css.map new file mode 100644 index 000000000..7ab561567 --- /dev/null +++ b/priv/static/static/css/9801.cfe503d4c949ae0c3813.css.map @@ -0,0 +1 @@ +{"version":3,"file":"static/css/9801.cfe503d4c949ae0c3813.css","mappings":"AACA,mBACE,qBACA,kBAGF,kBACE,gBACA,eACA,kBCRF,yBACE,qBACA,kBAGF,wBACE,gBACA,eACA,kBCRF,cACE,qBACA,kBAEA,8BACE,iBAIJ,eACE,gBACA,eACA,kBCXA,+BACE,cAEA,YACA,mBAFA,UAEA,CAGF,qCAEE,aACA,sBAFA,gBAGA,WAGF,6BACE,mBAEA,uEAEE,WCpBJ,2BACE,UAGF,kBAEE,iBAGA,eADA,kBAHA,uBAEA,kBAEA,CCRJ,uBACE,YAEA,qCACE,0CACA,qBACA,qBAEA,oFAEE,cACA,mBAEA,0GACE,gBAIJ,sDACE,aAEA,mEACE,SACA,kBAIJ,gDACE,mBAEA,kBADA,gBACA,CAGF,4CACE,eAGF,8CAGE,aADA,eADA,UAEA,CAGF,wGAEE,sBACA,SCnCW","sources":["webpack://pleroma_fe/./src/components/settings_modal/helpers/modified_indicator.vue","webpack://pleroma_fe/./src/components/settings_modal/helpers/profile_setting_indicator.vue","webpack://pleroma_fe/./src/components/settings_modal/helpers/draft_buttons.vue","webpack://pleroma_fe/./src/components/settings_modal/helpers/attachment_setting.vue","webpack://pleroma_fe/./src/components/settings_modal/admin_tabs/frontends_tab.scss","webpack://pleroma_fe/./src/components/settings_modal/settings_modal_admin_content.scss","webpack://pleroma_fe/./src/_variables.scss"],"sourcesContent":["\n.ModifiedIndicator {\n display: inline-block;\n position: relative;\n}\n\n.modified-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n.ProfileSettingIndicator {\n display: inline-block;\n position: relative;\n}\n\n.profilesetting-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n.DraftButtons {\n display: inline-block;\n position: relative;\n\n .button-default {\n margin-left: 0.5em;\n }\n}\n\n.draft-tooltip {\n margin: 0.5em 1em;\n min-width: 10em;\n text-align: center;\n}\n","\n.AttachmentSetting {\n .attachment {\n display: block;\n width: 100%;\n height: 15em;\n margin-bottom: 0.5em;\n }\n\n .attachment-input {\n margin-left: 1em;\n display: flex;\n flex-direction: column;\n width: 20em;\n }\n\n .controls {\n margin-bottom: 0.5em;\n\n input,\n button {\n width: 100%;\n }\n }\n}\n",".frontends-tab {\n .cards-list {\n padding: 0;\n }\n\n dd {\n text-overflow: ellipsis;\n word-wrap: nowrap;\n white-space: nowrap;\n overflow-x: hidden;\n max-width: 10em;\n }\n}\n","@import \"src/variables\";\n\n.settings_tab-switcher {\n height: 100%;\n\n .setting-item {\n border-bottom: 2px solid var(--fg, $fallback--fg);\n margin: 1em 1em 1.4em;\n padding-bottom: 1.4em;\n\n > div,\n > label {\n display: block;\n margin-bottom: 0.5em;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n .select-multiple {\n display: flex;\n\n .option-list {\n margin: 0;\n padding-left: 0.5em;\n }\n }\n\n &:last-child {\n border-bottom: none;\n padding-bottom: 0;\n margin-bottom: 1em;\n }\n\n select {\n min-width: 10em;\n }\n\n textarea {\n width: 100%;\n max-width: 100%;\n height: 100px;\n }\n\n .unavailable,\n .unavailable svg {\n color: var(--cRed, $fallback--cRed);\n color: $fallback--cRed;\n }\n }\n}\n","$main-color: #f58d2c;\n$main-background: white;\n$darkened-background: whitesmoke;\n\n$fallback--bg: #121a24;\n$fallback--fg: #182230;\n$fallback--faint: rgb(185 185 186 / 50%);\n$fallback--text: #b9b9ba;\n$fallback--link: #d8a070;\n$fallback--icon: #666;\n$fallback--lightBg: rgb(21 30 42);\n$fallback--lightText: #b9b9ba;\n$fallback--border: #222;\n$fallback--cRed: #f00;\n$fallback--cBlue: #0095ff;\n$fallback--cGreen: #0fa00f;\n$fallback--cOrange: orange;\n\n$fallback--alertError: rgb(211 16 20 / 50%);\n$fallback--alertWarning: rgb(111 111 20 / 50%);\n\n$fallback--panelRadius: 10px;\n$fallback--checkboxRadius: 2px;\n$fallback--btnRadius: 4px;\n$fallback--inputRadius: 4px;\n$fallback--tooltipRadius: 5px;\n$fallback--avatarRadius: 4px;\n$fallback--avatarAltRadius: 10px;\n$fallback--attachmentRadius: 10px;\n$fallback--chatMessageRadius: 10px;\n\n$fallback--buttonShadow: 0 0 2px 0 rgb(0 0 0 / 100%),\n 0 1px 0 0 rgb(255 255 255 / 20%) inset,\n 0 -1px 0 0 rgb(0 0 0 / 20%) inset;\n\n$status-margin: 0.75em;\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/app.48e52505beba5b9ab69b.css b/priv/static/static/css/app.48e52505beba5b9ab69b.css deleted file mode 100644 index ee1ea9cb4..000000000 --- a/priv/static/static/css/app.48e52505beba5b9ab69b.css +++ /dev/null @@ -1,2 +0,0 @@ -.modal-view{align-items:center;animation-duration:.2s;animation-name:modal-background-fadein;bottom:0;display:flex;justify-content:center;left:0;opacity:0;overflow:auto;pointer-events:none;position:fixed;right:0;top:0;z-index:var(--ZI_modals)}.modal-view>*{pointer-events:auto}.modal-view.modal-background{background-color:rgba(0,0,0,.5);pointer-events:auto}.modal-view.open{opacity:1}@keyframes modal-background-fadein{0%{background-color:transparent}to{background-color:rgba(0,0,0,.5)}}.vue-recycle-scroller{position:relative}.vue-recycle-scroller.direction-vertical:not(.page-mode){overflow-y:auto}.vue-recycle-scroller.direction-horizontal:not(.page-mode){overflow-x:auto}.vue-recycle-scroller.direction-horizontal{display:-webkit-box;display:-ms-flexbox;display:flex}.vue-recycle-scroller__slot{-webkit-box-flex:1;-ms-flex:auto 0 0px;flex:auto 0 0}.vue-recycle-scroller__item-wrapper{-webkit-box-flex:1;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-flex:1;flex:1;overflow:hidden;position:relative}.vue-recycle-scroller.ready .vue-recycle-scroller__item-view{left:0;position:absolute;top:0;will-change:transform}.vue-recycle-scroller.direction-vertical .vue-recycle-scroller__item-wrapper{width:100%}.vue-recycle-scroller.direction-horizontal .vue-recycle-scroller__item-wrapper{height:100%}.vue-recycle-scroller.ready.direction-vertical .vue-recycle-scroller__item-view{width:100%}.vue-recycle-scroller.ready.direction-horizontal .vue-recycle-scroller__item-view{height:100%}.resize-observer[data-v-b329ee4c]{background-color:transparent;border:none;opacity:0}.resize-observer[data-v-b329ee4c],.resize-observer[data-v-b329ee4c] object{display:block;height:100%;left:0;overflow:hidden;pointer-events:none;position:absolute;top:0;width:100%;z-index:-1}.login-form{display:flex;flex-direction:column;padding:.6em}.login-form .btn{min-height:2em;width:10em}.login-form .register{flex:1 1}.login-form .login-bottom{align-items:center;display:flex;flex-direction:row;justify-content:space-between;margin-top:1em}.login-form .form-group{display:flex;flex-direction:column;line-height:24px;padding:.3em .5em .6em}.login-form .form-bottom{display:flex;height:32px;padding:.5em}.login-form .form-bottom button{width:10em}.login-form .form-bottom p{display:flex;margin:.35em;padding:.35em}.login-form .error{animation-duration:.4s;animation-name:shakeError;animation-timing-function:ease-in-out;text-align:center}.media-upload{cursor:pointer}.media-upload .hidden-input-file{display:none}.ScopeSelector .scope{cursor:pointer;display:inline-block;min-height:1.3em;min-width:1.3em;text-align:center}.ScopeSelector .scope.selected svg{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.checkbox{display:inline-block;min-height:1.2em;position:relative}.checkbox-indicator{padding-left:1.2em;position:relative}.checkbox-indicator:before{background-color:#182230;background-color:var(--input,#182230);border-radius:2px;border-radius:var(--checkboxRadius,2px);box-shadow:inset 0 0 2px #000;box-shadow:var(--inputShadow);box-sizing:border-box;color:transparent;content:"✓";display:block;font-size:1.1em;height:1.1em;line-height:1.1em;overflow:hidden;position:absolute;right:0;text-align:center;top:0;transition:color .2s;vertical-align:top;width:1.1em}.checkbox.disabled .checkbox-indicator:before,.checkbox.disabled .label{opacity:.5}.checkbox.disabled .label{color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5))}.checkbox input[type=checkbox]:checked+.checkbox-indicator:before{color:#b9b9ba;color:var(--inputText,#b9b9ba)}.checkbox input[type=checkbox]:indeterminate+.checkbox-indicator:before{color:#b9b9ba;color:var(--inputText,#b9b9ba);content:"–"}.checkbox>span{margin-left:.5em}.popover-trigger-button{display:inline-block}.popover{box-shadow:2px 2px 3px rgba(0,0,0,.5);box-shadow:var(--popupShadow);max-width:calc(100vw - 20px);min-width:0;position:fixed;z-index:var(--ZI_popover_override,var(--ZI_popovers))}.popover-default{--faint:var(--popoverFaintText,$fallback--faint);--faintLink:var(--popoverFaintLink,$fallback--faint);--lightText:var(--popoverLightText,$fallback--lightText);--postLink:var(--popoverPostLink,$fallback--link);--postFaintLink:var(--popoverPostFaintLink,$fallback--link);--icon:var(--popoverIcon,$fallback--icon);background-color:#121a24;background-color:var(--popover,#121a24);border-radius:4px;border-radius:var(--btnRadius,4px);color:#b9b9ba;color:var(--popoverText,#b9b9ba)}.popover-default:after{bottom:0;box-shadow:1px 1px 4px rgba(0,0,0,.6);box-shadow:var(--panelShadow);content:"";left:0;pointer-events:none;position:absolute;right:0;top:0;z-index:3}.dropdown-menu{display:block;font-size:1em;list-style:none;max-width:100vw;padding:.5rem 0;text-align:left;white-space:nowrap;z-index:var(--ZI_popover_override,var(--ZI_popovers))}.dropdown-menu .dropdown-divider{border-top:1px solid #222;border-top:1px solid var(--border,#222);height:0;margin:.5rem 0;overflow:hidden}.dropdown-menu .dropdown-item{--btnText:var(--popoverText,$fallback--text);background-color:transparent;border:none;border-radius:0;box-shadow:none;box-sizing:border-box;clear:both;display:block;font-weight:400;height:100%;line-height:21px;overflow:hidden;padding:.5em .75em;text-align:inherit;white-space:nowrap;width:100%}.dropdown-menu .dropdown-item-icon svg{color:var(--menuPopoverIcon,#666);margin-right:.75rem;width:22px}.dropdown-menu .dropdown-item.-has-submenu .chevron-icon{margin-left:2rem;margin-right:.25rem}.dropdown-menu .dropdown-item:active,.dropdown-menu .dropdown-item:hover{--btnText:var(--selectedMenuPopoverText,$fallback--link);--faint:var(--selectedMenuPopoverFaintText,$fallback--faint);--faintLink:var(--selectedMenuPopoverFaintLink,$fallback--faint);--lightText:var(--selectedMenuPopoverLightText,$fallback--lightText);--icon:var(--selectedMenuPopoverIcon,$fallback--icon);background-color:#151e2a;background-color:var(--selectedMenuPopover,#151e2a);box-shadow:none}.dropdown-menu .dropdown-item:active svg,.dropdown-menu .dropdown-item:hover svg{--icon:var(--selectedMenuPopoverIcon,$fallback--icon);color:var(--selectedMenuPopoverIcon,#666)}.dropdown-menu .dropdown-item .menu-checkbox{background-color:#182230;background-color:var(--input,#182230);border-radius:0;box-shadow:inset 0 0 2px #000;box-shadow:var(--inputShadow);display:inline-block;line-height:22px;margin-right:.75em;max-height:22px;max-width:22px;min-height:22px;min-width:22px;text-align:center;vertical-align:middle}.dropdown-menu .dropdown-item .menu-checkbox.menu-checkbox-checked:after{content:"✓";font-size:1.25em}.dropdown-menu .dropdown-item .menu-checkbox.-radio{border-radius:9999px}.dropdown-menu .dropdown-item .menu-checkbox.-radio.menu-checkbox-checked:after{content:"•";font-size:2em}.dropdown-menu .button-default.dropdown-item,.dropdown-menu .button-default.dropdown-item i[class*=icon-]{color:#b9b9ba;color:var(--btnText,#b9b9ba)}.dropdown-menu .button-default.dropdown-item:active{background-color:#151e2a;background-color:var(--selectedMenuPopover,#151e2a);color:#d8a070;color:var(--selectedMenuPopoverText,#d8a070)}.dropdown-menu .button-default.dropdown-item:disabled{color:#b9b9ba;color:var(--btnDisabledText,#b9b9ba)}.dropdown-menu .button-default.dropdown-item.toggled{color:#b9b9ba;color:var(--btnToggledText,#b9b9ba)}.still-image{align-items:center;display:inline-flex;line-height:0;overflow:hidden;position:relative}.still-image canvas{bottom:0;left:0;position:absolute;right:0;top:0;visibility:var(--_still-image-canvas-visibility,visible)}.still-image canvas,.still-image img{height:100%;-o-object-fit:contain;object-fit:contain;width:100%}.still-image.animated:before{zoom:var(--_still_image-label-scale,1);background:hsla(0,0%,50%,.5);border-radius:5px;border-radius:var(--tooltipRadius,5px);color:#fff;content:"gif";display:block;font-size:.7em;left:.5em;line-height:1;padding:2px 4px;position:absolute;top:.5em;visibility:var(--_still-image-label-visibility,visible);z-index:2}.still-image.animated:hover canvas{display:none}.still-image.animated:hover:before{visibility:var(--_still-image-label-visibility,hidden)}.still-image.animated img{visibility:var(--_still-image-img-visibility,hidden)}.still-image.animated:hover img{visibility:visible}.emoji-picker{--faint:var(--popoverFaintText,$fallback--faint);--faintLink:var(--popoverFaintLink,$fallback--faint);--lightText:var(--popoverLightText,$fallback--lightText);--icon:var(--popoverIcon,$fallback--icon);background-color:#121a24;background-color:var(--popover,#121a24);color:#d8a070;color:var(--popoverText,#d8a070);display:flex;flex-direction:column;max-width:calc(100vw - 20px);width:25em}.emoji-picker-header-image{align-items:center;display:inline-flex;height:32px;justify-content:center;max-height:32px;max-width:32px;width:32px}.emoji-picker-header-image .still-image{height:100%;max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain;width:100%}.emoji-picker .keep-open,.emoji-picker .too-many-emoji{line-height:normal;padding:7px}.emoji-picker .too-many-emoji{display:flex;flex-direction:column}.emoji-picker .keep-open-label{display:flex;padding:0 7px}.emoji-picker .heading{display:flex;padding:10px 7px 5px}.emoji-picker .content{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.emoji-picker .emoji-tabs{display:flex;flex-flow:row nowrap;flex-grow:1;overflow-x:auto}.emoji-picker .additional-tabs{border-left:1px solid #666;border-left-color:var(--icon,#666);display:flex;flex:0 0 auto;padding-left:7px}.emoji-picker .additional-tabs,.emoji-picker .emoji-tabs{align-content:center;display:flex;flex-basis:auto}.emoji-picker .additional-tabs-item,.emoji-picker .emoji-tabs-item{align-items:center;cursor:pointer;display:flex;font-size:1.85em;height:32px;max-height:32px;max-width:32px;padding:0 7px;width:32px}.emoji-picker .additional-tabs-item.disabled,.emoji-picker .emoji-tabs-item.disabled{opacity:.5;pointer-events:none}.emoji-picker .additional-tabs-item.active,.emoji-picker .emoji-tabs-item.active{border-bottom:4px solid}.emoji-picker .additional-tabs-item.active svg,.emoji-picker .emoji-tabs-item.active svg{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.emoji-picker .sticker-picker{flex:1 1 auto}.emoji-picker .emoji-content,.emoji-picker .stickers-content{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.emoji-picker .emoji-content.hidden,.emoji-picker .stickers-content.hidden{opacity:0;pointer-events:none;position:absolute}.emoji-picker .emoji-search{flex:0 0 auto;padding:5px}.emoji-picker .emoji-search input{width:100%}.emoji-picker .emoji-groups{flex:1 1 1px;height:100%;-webkit-mask:linear-gradient(0deg,#fff 0,transparent) bottom no-repeat,linear-gradient(180deg,#fff 0,transparent) top no-repeat,linear-gradient(0deg,#fff,#fff);mask:linear-gradient(0deg,#fff 0,transparent) bottom no-repeat,linear-gradient(180deg,#fff 0,transparent) top no-repeat,linear-gradient(0deg,#fff,#fff);mask-composite:xor;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-size:100% 20px,100% 20px,auto;mask-size:100% 20px,100% 20px,auto;min-height:200px;overflow:auto;position:relative;transition:-webkit-mask-size .15s;transition:mask-size .15s;transition:mask-size .15s,-webkit-mask-size .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.emoji-picker .emoji-groups.scrolled-top{-webkit-mask-size:100% 20px,100% 0,auto;mask-size:100% 20px,100% 0,auto}.emoji-picker .emoji-groups.scrolled-bottom{-webkit-mask-size:100% 0,100% 20px,auto;mask-size:100% 0,100% 20px,auto}.emoji-picker .emoji-group{align-items:center;display:flex;flex-wrap:wrap;justify-content:left;padding-left:5px}.emoji-picker .emoji-group-title{font-size:.85em;margin:0;width:100%}.emoji-picker .emoji-group-title.disabled{display:none}.emoji-picker .emoji-item{align-items:center;box-sizing:border-box;cursor:pointer;display:flex;height:32px;justify-content:center;line-height:32px;margin:4px;width:32px}.emoji-picker .emoji-item .emoji-picker-emoji.-custom{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain}.emoji-picker .emoji-item .emoji-picker-emoji.-unicode{font-size:24px;overflow:hidden}.emoji-input{display:flex;flex-direction:column;position:relative}.emoji-input .emoji-picker-icon{cursor:pointer;font-size:1.3em;line-height:24px;margin:.2em .25em;position:absolute;right:0;top:0}.emoji-input .emoji-picker-icon:hover i{color:#b9b9ba;color:var(--text,#b9b9ba)}.emoji-input .emoji-picker-panel{margin-top:2px;position:absolute;z-index:20}.emoji-input .emoji-picker-panel.hide{display:none}.emoji-input input,.emoji-input textarea{flex:1 0 auto}.emoji-input.with-picker input{padding-right:30px}.emoji-input .hidden-overlay{bottom:0;color:red;left:0;opacity:0;overflow:hidden;pointer-events:none;position:absolute;right:0;top:0}.emoji-input .hidden-overlay .caret{border:1px solid red;margin-right:calc(-1ch - 1px);width:0}.autocomplete-panel{position:absolute}.autocomplete-item{border-bottom:1px solid rgba(0,0,0,.4);cursor:pointer;display:flex;height:32px;padding:.2em .4em}.autocomplete-item .image{font-size:32px;height:32px;line-height:32px;margin-right:4px;text-align:center;width:32px}.autocomplete-item .image img{height:32px;-o-object-fit:contain;object-fit:contain;width:32px}.autocomplete-item .label{display:flex;flex-direction:column;justify-content:center;margin:0 .1em 0 .2em}.autocomplete-item .label .displayText{line-height:1.5}.autocomplete-item .label .detailText{font-size:9px;line-height:9px}.autocomplete-item.highlighted{--faint:var(--selectedMenuPopoverFaintText,$fallback--faint);--faintLink:var(--selectedMenuPopoverFaintLink,$fallback--faint);--lightText:var(--selectedMenuPopoverLightText,$fallback--lightText);--icon:var(--selectedMenuPopoverIcon,$fallback--icon);background-color:#182230;background-color:var(--selectedMenuPopover,#182230);color:var(--selectedMenuPopoverText,#b9b9ba)}label.Select{padding:0}label.Select select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:none;color:#b9b9ba;color:var(--inputText,--text,#b9b9ba);font-family:sans-serif;font-family:var(--inputFont,sans-serif);font-size:1em;height:2em;line-height:16px;margin:0;padding:0 2em 0 .2em;width:100%;z-index:1}label.Select .select-down-icon{bottom:0;color:#b9b9ba;color:var(--inputText,#b9b9ba);height:100%;line-height:2;pointer-events:none;position:absolute;right:5px;top:0;width:.875em;z-index:0}.poll-form{display:flex;flex-direction:column;padding:0 .5em .5em}.poll-form .add-option{align-self:flex-start;padding-left:.1em;padding-top:.25em}.poll-form .poll-option{align-items:baseline;display:flex;justify-content:space-between;margin-bottom:.25em}.poll-form .input-container{width:100%}.poll-form .input-container input{padding-right:2.5em;width:100%}.poll-form .delete-option{margin-left:-1.5em;width:1.5em;z-index:1}.poll-form .poll-type-expiry{display:flex;margin-top:.5em;width:100%}.poll-form .poll-type{flex:1 1 60%;margin-right:.75em}.poll-form .poll-type .poll-type-select{padding-right:.75em}.poll-form .poll-expiry{display:flex}.poll-form .poll-expiry .expiry-amount{text-align:right;width:3em}.Flash{display:inline-block;position:relative}.Flash,.Flash .placeholder,.Flash .player{height:100%;width:100%}.Flash .placeholder{align-items:center;background:var(--bg);color:var(--link);display:flex;justify-content:center}.Flash .hider{top:0}.Flash .label{word-wrap:normal;flex:1 1 0;line-height:1.2;text-align:center;white-space:normal}.Flash .hidden{display:none;visibility:"hidden"}.Attachment{align-self:flex-start;border:1px solid #222;border-color:var(--border,#222);border-radius:10px;border-radius:var(--attachmentRadius,10px);display:inline-flex;flex-direction:column;height:100%;line-height:0;position:relative}.Attachment .attachment-wrapper{flex:1 1 auto;height:100%;overflow:hidden;position:relative}.Attachment .description-container{display:flex;flex:0 1 0;padding-top:.5em;z-index:1}.Attachment .description-container p{flex:1;line-height:1.5;margin:0;overflow:hidden;padding:.5em;text-align:center;text-overflow:ellipsis;white-space:nowrap}.Attachment .description-container.-static{background:var(--popover);bottom:0;box-shadow:var(--popupShadow);left:0;padding-top:0;position:absolute;right:0}.Attachment .description-field{flex:1;min-width:0}.Attachment .audio-container,.Attachment .flash-container,.Attachment .image-container,.Attachment .oembed-container,.Attachment .placeholder-container,.Attachment .video-container{display:flex;height:100%;justify-content:center;width:100%}.Attachment .image-container .image{height:100%;width:100%}.Attachment .flash-container .flash,.Attachment .flash-container video,.Attachment .video-container .flash,.Attachment .video-container video{align-self:center;height:100%;-o-object-fit:contain;object-fit:contain;width:100%}.Attachment .audio-container{align-items:flex-end;display:flex}.Attachment .audio-container audio{height:100%;width:100%}.Attachment .placeholder-container{align-items:center;display:flex;flex-direction:column;justify-content:center;padding-top:.5em}.Attachment .play-icon{color:hsla(0,0%,100%,.75);font-size:64px;left:calc(50% - 32px);position:absolute;text-shadow:0 0 2px rgba(0,0,0,.4);top:calc(50% - 32px)}.Attachment .play-icon:before{margin:0}.Attachment .attachment-buttons{display:flex;margin-right:.5em;margin-top:.5em;position:absolute;right:0;top:0;z-index:1}.Attachment .attachment-buttons .attachment-button{background:hsla(0,0%,90%,.7);border-radius:5px;border-radius:var(--tooltipRadius,5px);font-size:1.25em;height:2em;margin-left:.5em;padding:0;text-align:center;width:2em}.Attachment .attachment-buttons .attachment-button .svg-inline--fa{color:rgba(0,0,0,.6)}.Attachment .attachment-buttons .attachment-button:hover .svg-inline--fa{color:rgba(0,0,0,.9)}.Attachment.-contain-fit canvas,.Attachment.-contain-fit img{-o-object-fit:contain;object-fit:contain}.Attachment.-cover-fit canvas,.Attachment.-cover-fit img{-o-object-fit:cover;object-fit:cover}.Attachment .oembed-container{display:flex;flex:1 0 100%;line-height:1.2em;margin-right:15px;width:100%}.Attachment .oembed-container img{width:100%}.Attachment .oembed-container .image{flex:1}.Attachment .oembed-container .image img{border:0;border-radius:5px;height:100%;-o-object-fit:cover;object-fit:cover}.Attachment .oembed-container .text{flex:2;margin:8px;word-break:break-all}.Attachment .oembed-container .text h1{font-size:1rem;margin:0}.Attachment.-size-small .play-icon{zoom:.5;opacity:.7}.Attachment.-size-small .attachment-buttons{zoom:.7;opacity:.5}.Attachment.-editable{padding:.5em}.Attachment.-editable .attachment-buttons,.Attachment.-editable .description-container{margin:0}.Attachment.-placeholder{color:#d8a070;color:var(--postLink,#d8a070);display:inline-block;height:auto;line-height:1.5;overflow:hidden;white-space:nowrap}.Attachment.-placeholder:not(.-editable){border:none}.Attachment.-placeholder.-editable{align-items:baseline;display:flex;flex-direction:row}.Attachment.-placeholder.-editable .attachment-buttons,.Attachment.-placeholder.-editable .description-container{margin:0;padding:0;position:relative}.Attachment.-placeholder.-editable .description-container{flex:1;padding-left:.5em}.Attachment.-placeholder.-editable .attachment-buttons{align-self:center;order:99}.Attachment.-placeholder a{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis}.Attachment.-placeholder svg{color:inherit}.Attachment.-loading{cursor:progress}.Attachment.-compact .placeholder-container{padding-bottom:.5em}.Gallery .gallery-rows{display:flex;flex-direction:column}.Gallery .gallery-row{flex-grow:1;height:0;position:relative;width:100%}.Gallery .gallery-row .gallery-row-inner{align-content:stretch;bottom:0;display:flex;flex-flow:row wrap;left:0;position:absolute;right:0;top:0}.Gallery .gallery-row .gallery-row-inner .gallery-item{box-sizing:border-box;flex-grow:1;height:100%;margin:0 .5em 0 0;min-width:2em}.Gallery .gallery-row .gallery-row-inner .gallery-item:last-child{margin:0}.Gallery .gallery-row .gallery-row-inner.-grid{grid-gap:.5em;display:grid;grid-template-columns:repeat(auto-fill,minmax(15em,1fr));height:auto;position:relative;width:100%}.Gallery .gallery-row .gallery-row-inner.-grid .gallery-item{height:200px;margin:0}.Gallery .gallery-row.-grid,.Gallery .gallery-row.-minimal{height:auto}.Gallery .gallery-row.-grid .gallery-row-inner,.Gallery .gallery-row.-minimal .gallery-row-inner{position:relative}.Gallery .gallery-row:not(:first-child){margin-top:.5em}.Gallery.-long .gallery-rows{-webkit-mask:linear-gradient(0deg,#fff,transparent) bottom/100% 70px no-repeat,linear-gradient(0deg,#fff,#fff);mask:linear-gradient(0deg,#fff,transparent) bottom/100% 70px no-repeat,linear-gradient(0deg,#fff,#fff);mask-composite:xor;-webkit-mask-composite:xor;mask-composite:exclude;max-height:25em;overflow:hidden}.Gallery .many-attachments-text{line-height:2;text-align:center}.Gallery .many-attachments-buttons{display:flex}.Gallery .many-attachments-button{display:flex;flex:1;justify-content:center;line-height:2}.Gallery .many-attachments-button button{padding:0 2em}.Avatar{--_avatarShadowBox:var(--avatarStatusShadow);--_avatarShadowFilter:var(--avatarStatusShadowFilter);--_avatarShadowInset:var(--avatarStatusShadowInset);--_still-image-label-visibility:hidden;display:inline-block;height:48px;position:relative;width:48px}.Avatar.-compact{border-radius:10px;border-radius:var(--avatarAltRadius,10px);height:32px;width:32px}.Avatar .avatar{border-radius:4px;border-radius:var(--avatarRadius,4px);box-shadow:var(--_avatarShadowBox);height:100%;width:100%}.Avatar .avatar.-better-shadow{box-shadow:var(--_avatarShadowInset);filter:var(--_avatarShadowFilter)}.Avatar .avatar.-animated:before{display:none}.Avatar .avatar.-compact{border-radius:10px;border-radius:var(--avatarAltRadius,10px)}.Avatar .avatar.-placeholder{background-color:#182230;background-color:var(--fg,#182230)}.Avatar img{height:100%;width:100%}.Avatar .bot-indicator{background:hsla(0,0%,50%,.5);border-radius:var(--tooltipRadius);bottom:0;color:#fff;margin:-.2em;padding:.2em;position:absolute;right:0}.MentionLink{color:var(--link);display:inline;position:relative;white-space:normal;word-break:normal}.MentionLink .new,.MentionLink .original{border-radius:2px;display:inline}.MentionLink .mention-avatar{border-radius:var(--avatarAltRadius,10px);height:1.5em;margin-right:.2em;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1.5em}.MentionLink .full{word-wrap:normal;display:inline-block;height:100%;left:0;margin-top:.25em;opacity:0;padding:.5em;pointer-events:none;position:absolute;top:100%;transition:opacity .2s ease;-webkit-user-select:all;-moz-user-select:all;user-select:all;white-space:nowrap;z-index:1}.MentionLink .short.-with-tooltip,.MentionLink .you{-webkit-user-select:none;-moz-user-select:none;user-select:none}.MentionLink .full,.MentionLink .short{white-space:nowrap}.MentionLink .shortName{white-space:normal}.MentionLink .new.-you .shortName{font-weight:600}.MentionLink .new.-has-selection{background-color:var(--alertNeutral,#182230);color:var(--alertNeutralText,#b9b9ba)}.MentionLink .new .at{color:var(--link);display:inline-block;line-height:1;margin:0;opacity:.8;padding:0 .1em;vertical-align:-25%}.MentionLink .new.-striped .shortName{background-image:repeating-linear-gradient(135deg,var(--____highlight-tintColor),var(--____highlight-tintColor) 5px,var(--____highlight-tintColor2) 5px,var(--____highlight-tintColor2) 10px)}.MentionLink .new.-solid .shortName{background-image:linear-gradient(var(--____highlight-tintColor2),var(--____highlight-tintColor2))}.MentionLink .new.-side .shortName{box-shadow:0 -5px 3px -4px inset var(--____highlight-solidColor)}.MentionLink .serverName.-faded{color:var(--faintLink,#d8a070)}.mention-link-popover{max-height:20rem;max-width:70ch;overflow:hidden}.MentionsLine{word-break:break-all}.MentionsLine .mention-link:not(:first-child):before{content:" "}.MentionsLine .showMoreLess{color:var(--link);margin-left:.5em;white-space:normal}.HashtagLink{color:var(--link);display:inline-block;position:relative;white-space:normal}.RichContent blockquote{border-left:.2em solid var(--faint,hsla(240,1%,73%,.5));font-style:italic;margin:.2em 0 .2em .2em;padding-left:1em}.RichContent pre{overflow:auto}.RichContent code,.RichContent kbd,.RichContent pre,.RichContent samp,.RichContent var{font-family:var(--postCodeFont,monospace)}.RichContent p{margin:0 0 1em}.RichContent p:last-child{margin:0}.RichContent h1{font-size:1.1em;line-height:1.2em;margin:1.4em 0}.RichContent h2{font-size:1.1em;margin:1em 0}.RichContent h3{font-size:1em;margin:1.2em 0}.RichContent h4{margin:1.1em 0}.RichContent .emoji,.RichContent .img{display:inline-block}.RichContent .emoji{height:var(--emoji-size,32px);width:var(--emoji-size,32px)}.RichContent .img,.RichContent video{max-height:400px;max-width:100%;-o-object-fit:contain;object-fit:contain;vertical-align:middle}.poll .votes{display:flex;flex-direction:column;margin:0 0 .5em}.poll .poll-option{margin:.75em .5em}.poll .option-result{color:#b9b9ba;color:var(--lightText,#b9b9ba);display:flex;flex-direction:row;height:100%;position:relative}.poll .option-result-label{align-items:center;display:flex;padding:.1em .25em;word-break:break-word;z-index:1}.poll .result-percentage{flex-shrink:0;width:3.5em}.poll .result-fill{background-color:#151e2a;background-color:var(--poll,#151e2a);border-radius:10px;border-radius:var(--panelRadius,10px);color:#b9b9ba;color:var(--pollText,#b9b9ba);height:100%;left:0;position:absolute;top:0;transition:width .5s}.poll .option-vote{align-items:center;display:flex}.poll input{width:3.5em}.poll .footer{align-items:center;display:flex}.poll.loading *{cursor:progress}.poll .poll-vote-button{margin-right:.5em;padding:0 .5em}.poll .poll-checkbox{display:none}.StatusBody{display:flex;flex-direction:column}.StatusBody .emoji{--_still_image-label-scale:0.5}.StatusBody .attachments{margin-top:.5em}.StatusBody .summary,.StatusBody .text{word-wrap:break-word;font-family:var(--postFont,sans-serif);line-height:var(--post-line-height);overflow-wrap:break-word;white-space:pre-wrap;word-break:break-word}.StatusBody .summary{display:block;font-style:italic;padding-bottom:.5em}.StatusBody .text.-single-line{height:1.4em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.StatusBody .summary-wrapper{border-color:var(--border,#222);border-style:solid;border-width:0 0 1px;flex-grow:0;margin-bottom:.5em}.StatusBody .summary-wrapper.-tall{position:relative}.StatusBody .summary-wrapper.-tall .summary{max-height:2em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.StatusBody .text-wrapper{display:flex;flex-flow:column nowrap}.StatusBody .text-wrapper.-tall-status{height:220px;overflow-x:hidden;overflow-y:hidden;position:relative;z-index:1}.StatusBody .text-wrapper.-tall-status .media-body{-webkit-mask:linear-gradient(0deg,#fff,transparent) bottom/100% 70px no-repeat,linear-gradient(0deg,#fff,#fff);mask:linear-gradient(0deg,#fff,transparent) bottom/100% 70px no-repeat,linear-gradient(0deg,#fff,#fff);mask-composite:xor;-webkit-mask-composite:xor;mask-composite:exclude;min-height:0}.StatusBody .cw-status-hider,.StatusBody .status-unhider,.StatusBody .tall-status-hider,.StatusBody .tall-subject-hider{display:inline-block;text-align:center;width:100%;word-break:break-all}.StatusBody .tall-status-hider{height:70px;line-height:110px;margin-top:150px;position:absolute;z-index:2}.StatusBody .tall-subject-hider{padding-bottom:.5em}.StatusBody .cw-status-hider,.StatusBody .status-unhider{word-break:break-all}.StatusBody .cw-status-hider svg,.StatusBody .status-unhider svg{color:inherit}.StatusBody .greentext{color:#0fa00f;color:var(--postGreentext,#0fa00f)}.StatusBody .cyantext{color:var(--postCyantext,#0095ff)}.StatusBody.-compact{--emoji-size:16px;align-items:top;flex-direction:row}.StatusBody.-compact .attachments,.StatusBody.-compact .body{max-height:3.25em}.StatusBody.-compact .body{flex:5 1 auto;mask-composite:xor;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-image:linear-gradient(180deg,#fff 2em,transparent 3em);mask-image:linear-gradient(180deg,#fff 2em,transparent 3em);-webkit-mask-position:0 0,0 0;mask-position:0 0,0 0;-webkit-mask-repeat:repeat-x,repeat;mask-repeat:repeat-x,repeat;-webkit-mask-size:auto 3.5em,auto auto;mask-size:auto 3.5em,auto auto;min-width:5em;overflow:hidden;white-space:normal}.StatusBody.-compact .attachments{flex:1 1 0;height:100%;margin-left:.5em;margin-top:0;min-width:5em}.StatusBody.-compact .summary-wrapper{border:none;display:inline-block;line-height:inherit;margin:0}.StatusBody.-compact .summary-wrapper .summary:after{content:": "}.StatusBody.-compact .text-wrapper{display:inline-block}.link-preview-card{border:1px solid #222;border-color:var(--border,#222);border-radius:10px;border-radius:var(--attachmentRadius,10px);color:#b9b9ba;color:var(--text,#b9b9ba);cursor:pointer;display:flex;flex-direction:row;margin-top:.5em;overflow:hidden}.link-preview-card .card-image{flex-shrink:0;max-width:25%;width:120px}.link-preview-card .card-image img{border-radius:10px;border-radius:var(--attachmentRadius,10px);height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.link-preview-card .card-content{display:flex;flex-direction:column;margin:.5em;max-height:100%}.link-preview-card .card-host{font-size:.85em}.link-preview-card .card-description{line-height:1.2em;margin:.5em 0 0;max-height:calc(3.6em - 1px);overflow:hidden;text-overflow:ellipsis;word-break:break-word}.link-preview-card .nsfw-alert{margin:2em 0}.StatusContent{flex:1;min-width:0}.post-status-form{position:relative}.post-status-form .attachments{margin-bottom:.5em}.post-status-form .form-bottom{display:flex;height:2.5em;justify-content:space-between;padding:.5em}.post-status-form .form-bottom button{width:10em}.post-status-form .form-bottom p{display:flex;margin:.35em;padding:.35em}.post-status-form .form-bottom-left{display:flex;flex:1;margin-right:7px;max-width:10em;padding-right:7px}.post-status-form .preview-heading{display:flex;padding-left:.5em}.post-status-form .preview-toggle{cursor:pointer;flex:1;-webkit-user-select:none;-moz-user-select:none;user-select:none}.post-status-form .preview-toggle:hover{text-decoration:underline}.post-status-form .preview-toggle i,.post-status-form .preview-toggle svg{font-size:.8em;margin-left:.2em;transform:rotate(90deg)}.post-status-form .preview-container{margin-bottom:1em}.post-status-form .preview-error{color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5));font-style:italic}.post-status-form .preview-status{border:1px solid #222;border:1px solid var(--border,#222);border-radius:5px;border-radius:var(--tooltipRadius,5px);margin:0;padding:.5em}.post-status-form .text-format .only-format{color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5))}.post-status-form .visibility-tray{align-items:baseline;display:flex;justify-content:space-between;padding-top:5px}.post-status-form .visibility-notice.edit-warning>:first-child{margin-top:0}.post-status-form .visibility-notice.edit-warning>:last-child{margin-bottom:0}.post-status-form .media-upload-icon{justify-content:left;order:1}.post-status-form .emoji-icon{justify-content:center;order:2}.post-status-form .poll-icon{justify-content:right;order:3}.post-status-form .emoji-icon,.post-status-form .media-upload-icon,.post-status-form .poll-icon{align-items:center;display:flex;flex:1;font-size:1.85em;line-height:1.1;padding:0 .1em}.post-status-form .emoji-icon.selected i,.post-status-form .emoji-icon.selected label,.post-status-form .emoji-icon.selected svg,.post-status-form .emoji-icon:hover i,.post-status-form .emoji-icon:hover label,.post-status-form .emoji-icon:hover svg,.post-status-form .media-upload-icon.selected i,.post-status-form .media-upload-icon.selected label,.post-status-form .media-upload-icon.selected svg,.post-status-form .media-upload-icon:hover i,.post-status-form .media-upload-icon:hover label,.post-status-form .media-upload-icon:hover svg,.post-status-form .poll-icon.selected i,.post-status-form .poll-icon.selected label,.post-status-form .poll-icon.selected svg,.post-status-form .poll-icon:hover i,.post-status-form .poll-icon:hover label,.post-status-form .poll-icon:hover svg{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.post-status-form .emoji-icon.disabled i,.post-status-form .emoji-icon.disabled svg,.post-status-form .media-upload-icon.disabled i,.post-status-form .media-upload-icon.disabled svg,.post-status-form .poll-icon.disabled i,.post-status-form .poll-icon.disabled svg{color:#666;color:var(--btnDisabledText,#666);cursor:not-allowed}.post-status-form .emoji-icon.disabled i:hover,.post-status-form .emoji-icon.disabled svg:hover,.post-status-form .media-upload-icon.disabled i:hover,.post-status-form .media-upload-icon.disabled svg:hover,.post-status-form .poll-icon.disabled i:hover,.post-status-form .poll-icon.disabled svg:hover{color:#666;color:var(--btnDisabledText,#666)}.post-status-form .error{text-align:center}.post-status-form .media-upload-wrapper{margin-bottom:.5em;margin-right:.2em;width:18em}.post-status-form .media-upload-wrapper img,.post-status-form .media-upload-wrapper video{max-height:10em;-o-object-fit:contain;object-fit:contain}.post-status-form .media-upload-wrapper .video{max-height:10em}.post-status-form .media-upload-wrapper input{flex:1;width:100%}.post-status-form .status-input-wrapper{display:flex;flex-direction:column;position:relative;width:100%}.post-status-form .btn[disabled]{cursor:not-allowed}.post-status-form form{display:flex;flex-direction:column;margin:.6em;position:relative}.post-status-form .form-group{display:flex;flex-direction:column;line-height:1.85;padding:.25em .5em .5em}.post-status-form .form-post-body{box-sizing:content-box;height:calc(var(--post-line-height)*1em);min-height:calc(var(--post-line-height)*1em);overflow:hidden;padding-bottom:calc(var(--_padding) + var(--post-line-height)*1em);resize:none;transition:min-height .2s .1s}.post-status-form .form-post-body.scrollable-form{overflow-y:auto}.post-status-form .main-input{position:relative}.post-status-form .character-counter{bottom:0;margin:0 .5em;padding:0;position:absolute;right:0}.post-status-form .character-counter.error{color:red;color:var(--cRed,red)}@keyframes fade-in{0%{opacity:0}to{opacity:.6}}@keyframes fade-out{0%{opacity:.6}to{opacity:0}}.post-status-form .drop-indicator{align-items:center;background-color:#121a24;background-color:var(--bg,#121a24);border:2px dashed #b9b9ba;border:2px dashed var(--text,#b9b9ba);border-radius:5px;border-radius:var(--tooltipRadius,5px);color:#b9b9ba;color:var(--text,#b9b9ba);display:flex;font-size:5em;height:100%;justify-content:center;opacity:.6;position:absolute;width:100%}.remote-follow{max-width:220px}.remote-follow .remote-button{min-height:2em;width:100%}.dark-overlay:before{background:rgba(27,31,35,.5);bottom:0;content:" ";left:0;right:0;z-index:2000}.dark-overlay:before,.dialog-modal.panel{cursor:default;display:block;position:fixed;top:0}.dialog-modal.panel{background-color:#121a24;background-color:var(--bg,#121a24);left:50%;margin:15vh auto;max-height:80vh;max-width:90vw;transform:translateX(-50%);z-index:2001}.dialog-modal.panel .dialog-modal-heading .title{text-align:center}.dialog-modal.panel .dialog-modal-content{background-color:#121a24;background-color:var(--bg,#121a24);margin:0;padding:1rem;white-space:normal}.dialog-modal.panel .dialog-modal-footer{background-color:#121a24;background-color:var(--bg,#121a24);border-top:1px solid #222;border-top:1px solid var(--border,#222);display:flex;justify-content:flex-end;margin:0;padding:.5em}.dialog-modal.panel .dialog-modal-footer button{margin-left:.5rem;width:auto}.moderation-tools-popover{height:100%}.moderation-tools-popover .trigger{display:flex!important;height:100%}.moderation-tools-button i,.moderation-tools-button svg{font-size:.8em}.AccountActions .ellipsis-button{margin:-.5em 0;padding:.5em 0;text-align:center;width:2.5em}.AccountActions .ellipsis-button:not(:hover) .icon{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.user-note{display:flex;flex-direction:column}.user-note .heading{align-items:center;display:flex;flex-direction:row;justify-content:space-between;margin-bottom:.75em}.user-note .heading .btn{min-width:95px}.user-note .heading .buttons{display:flex;flex-direction:row;justify-content:right}.user-note .heading .buttons .btn{margin-left:.5em}.user-note .note-text{align-self:stretch}.user-note .note-text.-blank{color:var(--faint,hsla(240,1%,73%,.5));font-style:italic}.user-card{position:relative;z-index:1}.user-card:hover{--_still-image-img-visibility:visible;--_still-image-canvas-visibility:hidden;--_still-image-label-visibility:hidden}.user-card .panel-heading{align-items:stretch;background:transparent;box-shadow:none;flex-direction:column;padding:.5em 0;position:relative;text-align:center}.user-card .panel-body{word-wrap:break-word;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit;position:relative}.user-card .background-image{background-color:var(--profileBg);background-size:cover;border-bottom-left-radius:calc(var(--__roundnessBottom, --panelRadius) - 1px);border-bottom-right-radius:calc(var(--__roundnessBottom, --panelRadius) - 1px);border-top-left-radius:calc(var(--__roundnessTop, --panelRadius) - 1px);border-top-right-radius:calc(var(--__roundnessTop, --panelRadius) - 1px);bottom:0;left:0;-webkit-mask:linear-gradient(0deg,#fff,transparent) bottom no-repeat,linear-gradient(0deg,#fff,#fff);mask:linear-gradient(0deg,#fff,transparent) bottom no-repeat,linear-gradient(0deg,#fff,#fff);mask-composite:xor;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-size:100% 60%;mask-size:100% 60%;position:absolute;right:0;top:0;z-index:-2}.user-card .background-image.hide-bio{-webkit-mask-size:100% 40px;mask-size:100% 40px}.user-card-bio{display:block;line-height:1.3;margin:0;padding:1em;text-align:center}.user-card-bio a{color:#d8a070;color:var(--postLink,#d8a070)}.user-card-bio img{max-height:400px;max-width:100%;-o-object-fit:contain;object-fit:contain;vertical-align:middle}.user-card.-rounded-t{--__roundnessTop:var(--panelRadius);--__roundnessBottom:0;border-top-left-radius:10px;border-top-left-radius:var(--panelRadius,10px);border-top-right-radius:10px;border-top-right-radius:var(--panelRadius,10px)}.user-card.-rounded{--__roundnessTop:var(--panelRadius);--__roundnessBottom:var(--panelRadius);border-radius:10px;border-radius:var(--panelRadius,10px)}.user-card.-popover{--__roundnessTop:var(--tooltipRadius);--__roundnessBottom:var(--tooltipRadius);border-radius:5px;border-radius:var(--tooltipRadius,5px)}.user-card.-bordered{border:1px solid #222;border-color:var(--border,#222)}.user-info{padding:0 26px}.user-info,.user-info a{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.user-info a:hover{color:var(--icon)}.user-info .container{align-items:flex-start;display:flex;max-height:56px;min-width:0;padding:16px 0 6px}.user-info .container>*{min-width:0}.user-info .container>a{display:flex;vertical-align:middle}.user-info .container .Avatar{--_avatarShadowBox:var(--avatarShadow);--_avatarShadowFilter:var(--avatarShadowFilter);--_avatarShadowInset:var(--avatarShadowInset);height:56px;-o-object-fit:cover;object-fit:cover;width:56px}.user-info-avatar{cursor:pointer;position:relative}.user-info-avatar.-overlay{align-items:center;background-color:rgba(0,0,0,.3);border-radius:4px;border-radius:var(--avatarRadius,4px);bottom:0;display:flex;justify-content:center;left:0;opacity:0;position:absolute;right:0;top:0;transition:opacity .2s ease}.user-info-avatar.-overlay svg{color:#fff}.user-info-avatar:hover .user-info-avatar.-overlay{opacity:1}.user-info .edit-profile-button,.user-info .external-link-button{cursor:pointer;margin:-.5em 0;padding:.5em 0;text-align:center;width:2.5em}.user-info .edit-profile-button:not(:hover) .icon,.user-info .external-link-button:not(:hover) .icon{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.user-info .bottom-line{align-items:baseline;font-size:1.1em;font-weight:light}.user-info .bottom-line .lock-icon{margin-left:.5em}.user-info .bottom-line .user-screen-name{flex:0 1 auto;min-width:1px;overflow:hidden;text-overflow:ellipsis}.user-info .bottom-line .dailyAvg{color:#b9b9ba;color:var(--text,#b9b9ba);flex:0 0 auto;font-size:.7em;margin-left:1em;min-width:1px}.user-info .bottom-line .user-role{background-color:#182230;background-color:var(--alertNeutral,#182230);color:#b9b9ba;color:var(--alertNeutralText,#b9b9ba);flex:none}.user-info .user-summary{--emoji-size:1.7em;display:block;flex:1 1 0;line-height:2em;margin-left:.6em;text-align:left;text-overflow:ellipsis;white-space:nowrap;z-index:1}.user-info .user-summary .bottom-line,.user-info .user-summary .top-line{display:flex}.user-info .user-name{flex:1 1 auto;font-size:1.1em;margin-right:1em;overflow:hidden;text-overflow:ellipsis}.user-info .user-meta{align-items:baseline;display:flex;flex-wrap:wrap;line-height:22px;margin-bottom:.15em}.user-info .user-meta .following{flex:1 0 auto;margin:0 0 .25em;text-align:left}.user-info .user-meta .highlighter{align-self:start;display:flex;flex:0 1 auto;flex-wrap:wrap;margin-right:-.5em}.user-info .user-meta .highlighter .userHighlightCl{flex:1 0 auto;padding:2px 10px}.user-info .user-meta .highlighter .userHighlightSel{flex:1 0 auto;padding-bottom:0;padding-top:0}.user-info .user-meta .highlighter .userHighlightText{flex:1 0 auto;width:70px}.user-info .user-meta .highlighter .userHighlightCl,.user-info .user-meta .highlighter .userHighlightSel,.user-info .user-meta .highlighter .userHighlightText{margin-bottom:.25em;margin-right:.5em;vertical-align:top}.user-info .user-interactions{display:flex;flex-flow:row wrap;margin-right:-.75em;position:relative}.user-info .user-interactions>*{margin:0 .75em .6em 0;min-width:95px;white-space:nowrap}.user-info .user-interactions button{margin:0}.user-info .user-note{margin:0 .75em .6em 0}.sidebar .edit-profile-button{display:none}.user-counts{color:#b9b9ba;color:var(--lightText,#b9b9ba);display:flex;flex-wrap:wrap;justify-content:space-between;line-height:16px;padding:.5em 1.5em 0;text-align:center}.user-count{flex:1 0 auto;margin:0 .5em;padding:.5em 0}.user-count h5{font-size:1em;font-weight:bolder;margin:0 0 .25em}.user-count a{text-decoration:none}.mute-expiry{display:flex;flex-direction:row}.user-panel .signed-in{overflow:visible;z-index:10}.NavigationEntry{align-items:baseline;box-sizing:border-box;color:#d8a070;color:var(--link,#d8a070);display:flex;height:3.5em;line-height:3.5em;padding:0 1em;width:100%}.NavigationEntry .timelines-chevron{margin-right:0}.NavigationEntry .main-link{flex:1}.NavigationEntry .menu-icon{margin-right:.8em}.NavigationEntry .extra-button{text-align:center;width:3em}.NavigationEntry .extra-button:last-child{margin-right:-.8em}.NavigationEntry:hover{--faint:var(--selectedMenuFaintText,$fallback--faint);--faintLink:var(--selectedMenuFaintLink,$fallback--faint);--lightText:var(--selectedMenuLightText,$fallback--lightText);background-color:#151e2a;background-color:var(--selectedMenu,#151e2a);color:#d8a070;color:var(--selectedMenuText,#d8a070)}.NavigationEntry:hover .menu-icon{--icon:var(--text,$fallback--icon)}.NavigationEntry.-active{--faint:var(--selectedMenuFaintText,$fallback--faint);--faintLink:var(--selectedMenuFaintLink,$fallback--faint);--lightText:var(--selectedMenuLightText,$fallback--lightText);background-color:#151e2a;background-color:var(--selectedMenu,#151e2a);color:#b9b9ba;color:var(--selectedMenuText,#b9b9ba);font-weight:bolder}.NavigationEntry.-active .menu-icon{--icon:var(--text,$fallback--icon)}.NavigationEntry.-active:hover{text-decoration:underline}.NavigationPins{display:flex;flex-wrap:wrap;height:100%;overflow:hidden}.NavigationPins .alert-dot{background-color:red;background-color:var(--badgeNotification,red);border-radius:100%;height:.5em;position:absolute;right:calc(50% - .75em);top:calc(50% - .5em);width:.5em}.NavigationPins .pinned-item{box-sizing:border-box;flex:1 0 3em;height:100%;min-width:2em;overflow:visible;position:relative;text-align:center}.NavigationPins .pinned-item .iconLetter,.NavigationPins .pinned-item .svg-inline--fa{margin:0}.NavigationPins .pinned-item.router-link-active{border-bottom:4px solid;color:#b9b9ba;color:var(--panelText,#b9b9ba)}.NavigationPins .pinned-item.router-link-active .iconLetter,.NavigationPins .pinned-item.router-link-active .svg-inline--fa{color:inherit}.NavPanel .panel{box-shadow:var(--panelShadow);overflow:hidden}.NavPanel ul{list-style:none;margin:0;padding:0}.NavPanel li{border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);position:relative}.NavPanel>li:first-child .menu-item{border-top-left-radius:10px;border-top-left-radius:var(--panelRadius,10px);border-top-right-radius:10px;border-top-right-radius:var(--panelRadius,10px)}.NavPanel>li:last-child .menu-item{border-bottom-left-radius:10px;border-bottom-left-radius:var(--panelRadius,10px);border-bottom-right-radius:10px;border-bottom-right-radius:var(--panelRadius,10px)}.NavPanel li:last-child{border:none}.NavPanel .navigation-chevron{margin-right:.8em}.NavPanel .navigation-chevron,.NavPanel .timelines-chevron{font-size:1.1em;margin-left:.8em}.NavPanel .timelines-background{background-color:#151e2a;background-color:var(--selectedMenu,#151e2a);border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);padding:0 0 0 .6em}.NavPanel .timelines{background-color:#121a24;background-color:var(--bg,#121a24)}.NavPanel .nav-panel-heading{--panel-heading-height-padding:0px}.features-panel li{line-height:24px}.who-to-follow *{vertical-align:middle}.who-to-follow img{height:32px;width:32px}.who-to-follow{margin:0;padding:0 1em}.who-to-follow-items{margin:1em 0;overflow:hidden;padding:0;text-overflow:ellipsis;white-space:nowrap}.who-to-follow-more{margin:1em 0;padding:0;text-align:center}.floating-shout{bottom:.5em;max-width:25em;position:fixed;z-index:var(--ZI_popovers)}.floating-shout.-left{left:.5em}.floating-shout:not(.-left){right:.5em}.shout-panel .shout-heading{cursor:pointer}.shout-panel .shout-heading .icon{color:#b9b9ba;color:var(--panelText,#b9b9ba);margin-right:.5em}.shout-panel .shout-heading .title{align-items:center;display:flex;justify-content:space-between}.shout-panel .shout-window{max-height:20em;overflow-x:hidden;overflow-y:auto}.shout-panel .shout-window-container{height:100%}.shout-panel .shout-message{display:flex;padding:.2em .5em}.shout-panel .shout-avatar img{border-radius:4px;border-radius:var(--avatarRadius,4px);height:24px;margin-right:.5em;margin-top:.25em;width:24px}.shout-panel .shout-input{display:flex}.shout-panel .shout-input textarea{flex:1;margin:.6em;min-height:3.5em;resize:none}.shout-panel .shout-panel .title{display:flex;justify-content:space-between}@keyframes media-fadein{0%{opacity:0}to{opacity:1}}.media-modal-view .modal-image-container{max-height:100%;max-width:100%;overflow:hidden}.media-modal-view .modal-image-container,.media-modal-view .modal-image-container-inner{align-items:center;display:flex;flex-direction:column;flex-grow:1;height:100%;justify-content:center;width:100%}.media-modal-view .counter,.media-modal-view .description{color:#fff;margin-top:1em;padding:.2em 2em;text-shadow:0 0 10px #000,0 0 10px #000}.media-modal-view .description{flex:0 0 auto;max-height:9.5em;max-width:500px;min-height:1em;overflow-y:auto;word-break:break-all}.media-modal-view .modal-image{animation:media-fadein .1s cubic-bezier(.7,0,1,.6);image-orientation:from-image;max-height:100%;max-width:100%}.media-modal-view .modal-image.loading{opacity:.5}.media-modal-view .loading-spinner{align-items:center;display:flex;height:100%;justify-content:center;pointer-events:none;position:absolute;width:100%}.media-modal-view .loading-spinner svg{color:#fff}.media-modal-view .modal-view-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;box-shadow:none;cursor:pointer;height:3em;opacity:0;overflow:visible;padding:0;transition:opacity 333ms cubic-bezier(.4,0,.22,1);width:3em}.media-modal-view .modal-view-button .button-icon{background-color:rgba(0,0,0,.3);color:#fff;font-size:1rem;height:3em;line-height:3em;position:absolute;text-align:center;width:3em}.media-modal-view .modal-view-button-arrow{display:block;height:3em;margin-top:1.5em;position:absolute;top:50%;width:3em}.media-modal-view .modal-view-button-arrow .arrow-icon{background-color:rgba(0,0,0,.3);color:#fff;line-height:3em;position:absolute;text-align:center;top:0}.media-modal-view .modal-view-button-arrow--prev{left:0}.media-modal-view .modal-view-button-arrow--prev .arrow-icon{left:.5em}.media-modal-view .modal-view-button-arrow--next{right:0}.media-modal-view .modal-view-button-arrow--next .arrow-icon{right:.5em}.media-modal-view .modal-view-button-hide{position:absolute;right:0;top:0}.media-modal-view .modal-view-button-hide .button-icon{right:.5em;top:.5em}.modal-view.media-modal-view{flex-direction:column;overflow:hidden;z-index:var(--ZI_media_modal)}.modal-view.media-modal-view .modal-view-button-arrow,.modal-view.media-modal-view .modal-view-button-hide{opacity:.75}.modal-view.media-modal-view .modal-view-button-arrow:focus,.modal-view.media-modal-view .modal-view-button-arrow:hover,.modal-view.media-modal-view .modal-view-button-hide:focus,.modal-view.media-modal-view .modal-view-button-hide:hover{box-shadow:none;outline:none}.modal-view.media-modal-view .modal-view-button-arrow:hover,.modal-view.media-modal-view .modal-view-button-hide:hover{opacity:1}.side-drawer-container{align-items:stretch;display:flex;height:100%;left:0;position:fixed;top:0;transition-duration:0s;transition-property:transform;width:100%;z-index:var(--ZI_navbar)}.side-drawer-container-open{transform:translate(0)}.side-drawer-container-closed{transform:translate(-100%);transition-delay:.35s}.side-drawer-darken{background-color:rgba(0,0,0,.5);height:100vh;left:0;position:fixed;top:0;transition:.35s;transition-property:background-color;width:100vw;z-index:-1}.side-drawer-darken-closed{background-color:transparent}.side-drawer-click-outside{flex:1 1 100%}.side-drawer{--faint:var(--popoverFaintText,$fallback--faint);--faintLink:var(--popoverFaintLink,$fallback--faint);--lightText:var(--popoverLightText,$fallback--lightText);--icon:var(--popoverIcon,$fallback--icon);background-color:#121a24;background-color:var(--popover,#121a24);box-shadow:1px 1px 4px rgba(0,0,0,.6);box-shadow:var(--panelShadow);color:#d8a070;color:var(--popoverText,#d8a070);flex:0 0 80%;margin:0 0 0 -100px;max-width:20em;overflow-x:hidden;padding:0 0 1em 100px;transition:.35s;transition-property:transform;transition-timing-function:cubic-bezier(0,1,.5,1);width:80%}.side-drawer .badge{margin-left:10px}.side-drawer-logo-wrapper{align-items:center;display:flex;padding:.85em}.side-drawer-logo-wrapper img{flex:none;height:50px;margin-right:.85em}.side-drawer-logo-wrapper span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.side-drawer-click-outside-closed{flex:0 0 0}.side-drawer-closed{transform:translate(-100%)}.side-drawer-heading{align-items:stretch;background:transparent;display:flex;flex-direction:column;margin:0;padding:0}.side-drawer ul{border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);list-style:none;margin:0;padding:0}.side-drawer ul:last-child{border:0}.side-drawer li{padding:0}.side-drawer li a,.side-drawer li button{box-sizing:border-box;display:block;height:3em;line-height:3em;padding:0 .7em}.side-drawer li a:hover,.side-drawer li button:hover{--faint:var(--selectedMenuPopoverFaintText,$fallback--faint);--faintLink:var(--selectedMenuPopoverFaintLink,$fallback--faint);--lightText:var(--selectedMenuPopoverLightText,$fallback--lightText);--icon:var(--selectedMenuPopoverIcon,$fallback--icon);background-color:#151e2a;background-color:var(--selectedMenuPopover,#151e2a);color:#b9b9ba;color:var(--selectedMenuPopoverText,#b9b9ba)}.MobilePostButton.button-default{align-items:center;background-color:#182230;background-color:var(--btn,#182230);border-radius:100%;bottom:1.5em;box-shadow:0 2px 2px rgba(0,0,0,.3),0 4px 6px rgba(0,0,0,.3);display:flex;height:5em;justify-content:center;position:fixed;right:1.5em;transition:transform .35s;transition-timing-function:cubic-bezier(0,1,.5,1);width:5em;z-index:10}.MobilePostButton.hidden{transform:translateY(150%)}.MobilePostButton svg{color:#b9b9ba;color:var(--text,#b9b9ba);font-size:1.5em}@media (min-width:801px){.new-status-button:not(.always-show){display:none}}.ReplyButton{display:flex}.ReplyButton>:first-child{margin:-10px -8px -10px -10px;padding:10px}.ReplyButton .action-counter{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ReplyButton .interactive.-active .svg-inline--fa,.ReplyButton .interactive:hover .svg-inline--fa{color:#0095ff;color:var(--cBlue,#0095ff)}.ReplyButton .interactive .focus-marker{visibility:hidden}.ReplyButton .interactive:focus:not(:focus-visible,:hover) .focus-marker{visibility:hidden}.ReplyButton .interactive:focus .focus-marker,.ReplyButton .interactive:hover .focus-marker{visibility:visible}.ReplyButton .interactive:focus-visible .focus-marker{visibility:visible}.FavoriteButton{display:flex}.FavoriteButton>:first-child{margin:-10px -8px -10px -10px;padding:10px}.FavoriteButton .action-counter{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.FavoriteButton .interactive .svg-inline--fa{animation-duration:.6s}.FavoriteButton .interactive.-favorited .svg-inline--fa,.FavoriteButton .interactive:hover .svg-inline--fa{color:orange;color:var(--cOrange,orange)}.FavoriteButton .interactive .focus-marker{visibility:hidden}.FavoriteButton .interactive .active-marker{visibility:visible}.FavoriteButton .interactive:focus:not(:focus-visible,:hover) .focus-marker{visibility:hidden}.FavoriteButton .interactive:focus:not(:focus-visible,:hover) .active-marker{visibility:visible}.FavoriteButton .interactive:focus .focus-marker,.FavoriteButton .interactive:hover .focus-marker{visibility:visible}.FavoriteButton .interactive:focus .active-marker,.FavoriteButton .interactive:hover .active-marker{visibility:hidden}.FavoriteButton .interactive:focus-visible .focus-marker{visibility:visible}.FavoriteButton .interactive:focus-visible .active-marker{visibility:hidden}.ReactButton .reaction-picker-filter{display:flex;padding:.5em}.ReactButton .reaction-picker-filter input{flex:1}.ReactButton .reaction-picker-divider{background-color:var(--border,#222);height:1px;margin:.5em;width:100%}.ReactButton .reaction-picker{align-content:flex-start;display:flex;flex-wrap:wrap;font-size:1.5em;height:9em;-webkit-mask:linear-gradient(0deg,#fff 0,transparent) bottom no-repeat,linear-gradient(180deg,#fff 0,transparent) top no-repeat,linear-gradient(0deg,#fff,#fff);mask:linear-gradient(0deg,#fff 0,transparent) bottom no-repeat,linear-gradient(180deg,#fff 0,transparent) top no-repeat,linear-gradient(0deg,#fff,#fff);mask-composite:xor;-webkit-mask-composite:xor;mask-composite:exclude;-webkit-mask-size:100% 20px,100% 20px,auto;mask-size:100% 20px,100% 20px,auto;overflow-y:scroll;padding:.5em;text-align:center;transition:-webkit-mask-size .15s;transition:mask-size .15s;transition:mask-size .15s,-webkit-mask-size .15s;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:10em}.ReactButton .reaction-picker .emoji-button{align-content:center;cursor:pointer;flex-basis:20%;line-height:1.5}.ReactButton .reaction-picker .emoji-button:hover{transform:scale(1.25)}.ReactButton .popover-trigger{margin:-10px;padding:10px}.ReactButton .popover-trigger:hover .svg-inline--fa{color:#b9b9ba;color:var(--text,#b9b9ba)}.ReactButton .popover-trigger-button{width:auto}.ReactButton .popover-trigger-button .focus-marker{visibility:hidden}.ReactButton .popover-trigger-button:focus:not(:focus-visible,:hover) .focus-marker{visibility:hidden}.ReactButton .popover-trigger-button:focus .focus-marker,.ReactButton .popover-trigger-button:hover .focus-marker{visibility:visible}.ReactButton .popover-trigger-button:focus-visible .focus-marker{visibility:visible}.RetweetButton{display:flex}.RetweetButton>:first-child{margin:-10px -8px -10px -10px;padding:10px}.RetweetButton .action-counter{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.RetweetButton .interactive .svg-inline--fa{animation-duration:.6s}.RetweetButton .interactive.-repeated .svg-inline--fa,.RetweetButton .interactive:hover .svg-inline--fa{color:#0fa00f;color:var(--cGreen,#0fa00f)}.RetweetButton .interactive .focus-marker{visibility:hidden}.RetweetButton .interactive .active-marker{visibility:visible}.RetweetButton .interactive:focus:not(:focus-visible,:hover) .focus-marker{visibility:hidden}.RetweetButton .interactive:focus:not(:focus-visible,:hover) .active-marker{visibility:visible}.RetweetButton .interactive:focus .focus-marker,.RetweetButton .interactive:hover .focus-marker{visibility:visible}.RetweetButton .interactive:focus .active-marker,.RetweetButton .interactive:hover .active-marker{visibility:hidden}.RetweetButton .interactive:focus-visible .focus-marker{visibility:visible}.RetweetButton .interactive:focus-visible .active-marker{visibility:hidden}.ExtraButtons .popover-trigger{margin:-10px;padding:10px;position:static}.ExtraButtons .popover-trigger:hover .svg-inline--fa{color:#b9b9ba;color:var(--text,#b9b9ba)}.ExtraButtons .popover-trigger-button{width:auto}.ExtraButtons .popover-trigger-button .focus-marker{visibility:hidden}.ExtraButtons .popover-trigger-button:focus:not(:focus-visible,:hover) .focus-marker{visibility:hidden}.ExtraButtons .popover-trigger-button:focus .focus-marker,.ExtraButtons .popover-trigger-button:hover .focus-marker{visibility:visible}.ExtraButtons .popover-trigger-button:focus-visible .focus-marker{visibility:visible}.avatars{display:flex;flex-wrap:wrap;height:24px;margin:0;padding:0}.avatars .avatars-item{margin:0 0 5px 5px}.avatars .avatars-item:first-child{padding-left:5px}.avatars .avatars-item .avatar-small{border-radius:10px;border-radius:var(--avatarAltRadius,10px);height:24px;width:24px}.status-popover.popover{border-color:#222;border-color:var(--border,#222);border-radius:5px;border-radius:var(--tooltipRadius,5px);border-style:solid;border-width:1px;font-size:1rem;max-width:95%;min-width:15em}.status-popover.popover .Status.Status{border:none}.status-popover.popover .status-preview-no-content{padding:1em;text-align:center}.status-popover.popover .status-preview-no-content i{font-size:2em}.user-list-popover{--emoji-size:16px;padding:.5em}.user-list-popover .user-list-row{display:flex;flex-direction:row;padding:.25em}.user-list-popover .user-list-row .user-list-names{display:flex;flex-direction:column;margin-left:.5em;min-width:5em}.user-list-popover .user-list-row .user-list-names img{height:1em;width:1em}.user-list-popover .user-list-row .user-list-screen-name{font-size:.65em}.EmojiReactions{display:flex;flex-wrap:wrap;margin-top:.25em}.EmojiReactions .emoji-reaction{align-items:center;box-sizing:border-box;display:flex;justify-content:center;margin-right:.5em;margin-top:.5em;padding:0 .5em}.EmojiReactions .emoji-reaction .reaction-emoji{margin-right:.25em;width:1.25em}.EmojiReactions .emoji-reaction:focus{outline:none}.EmojiReactions .emoji-reaction.not-clickable{cursor:default}.EmojiReactions .emoji-reaction.not-clickable:hover{box-shadow:0 0 2px 0 #000,inset 0 1px 0 0 hsla(0,0%,100%,.2),inset 0 -1px 0 0 rgba(0,0,0,.2);box-shadow:var(--buttonShadow)}.EmojiReactions .emoji-reaction.-picked-reaction{border:1px solid var(--accent,#d8a070);margin-left:-1px;margin-right:calc(.5em - 1px)}.EmojiReactions .emoji-reaction-expand{align-items:center;display:flex;justify-content:center;margin-right:.5em;margin-top:.5em;padding:0 .5em}.EmojiReactions .emoji-reaction-expand:hover{text-decoration:underline}.Status{word-wrap:break-word;min-width:0;white-space:normal;word-break:break-word}.Status:hover{--_still-image-img-visibility:visible;--_still-image-canvas-visibility:hidden;--_still-image-label-visibility:hidden}.Status.-focused{--lightText:var(--selectedPostLightText,$fallback--light);--faint:var(--selectedPostFaintText,$fallback--faint);--faintLink:var(--selectedPostFaintLink,$fallback--faint);--postLink:var(--selectedPostPostLink,$fallback--faint);--postFaintLink:var(--selectedPostFaintPostLink,$fallback--faint);--icon:var(--selectedPostIcon,$fallback--icon);background-color:#151e2a;background-color:var(--selectedPost,#151e2a);color:#b9b9ba;color:var(--selectedPostText,#b9b9ba)}.Status .gravestone{color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5));display:flex;padding:var(--status-margin,.75em)}.Status .gravestone .deleted-text{align-items:center;margin:.5em 0}.Status .status-container{display:flex;padding:var(--status-margin,.75em)}.Status .status-container>*{min-width:0}.Status .status-container.-repeat{padding-top:0}.Status .pin{align-items:center;display:flex;justify-content:flex-end;padding:var(--status-margin,.75em) var(--status-margin,.75em) 0}._misclick-prevention .Status{pointer-events:none}._misclick-prevention .Status .attachments{cursor:auto;pointer-events:auto}.Status .left-side{margin-right:var(--status-margin,.75em)}.Status .right-side{flex:1;min-width:0}.Status .usercard{margin-bottom:var(--status-margin,.75em)}.Status .status-username{--_still_image-label-scale:0.25;--emoji-size:14px;flex-shrink:1;font-weight:700;margin-right:.4em;max-width:85%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.Status .status-favicon{height:18px;margin-right:.4em;width:18px}.Status .status-heading{margin-bottom:.5em}.Status .heading-name-row{display:flex;justify-content:space-between;line-height:1.3}.Status .heading-name-row a{display:inline-block;word-break:break-all}.Status .account-name{flex:1 1 0;margin-right:.4em;min-width:1.6em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.Status .heading-left{display:flex;min-width:0}.Status .heading-right{display:flex;flex-shrink:0}.Status .heading-right .button-unstyled{margin:-5px;padding:5px}.Status .heading-right .button-unstyled:hover svg{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.Status .heading-right .svg-inline--fa{margin-left:.25em}.Status .glued-label{display:inline-flex;white-space:nowrap}.Status .timeago{margin-right:.2em}.Status .heading-edited-row,.Status .heading-reply-row{align-content:baseline;align-items:stretch;font-size:.85em;line-height:130%;margin-top:.2em;max-width:100%;position:relative}.Status .mentions,.Status .reply-to-no-popover,.Status .reply-to-popover{flex-shrink:0;margin-right:.4em;min-width:0}.Status .reply-glued-label{margin-right:.5em}.Status .reply-to-popover .reply-to:hover:before{border-bottom:1px solid var(--faint);bottom:0;content:"";display:block;pointer-events:none;position:absolute;width:100%}.Status .reply-to-popover .faint-link:hover{text-decoration:none}.Status .reply-to-popover.-strikethrough .reply-to:after{border-bottom:1px solid var(--faint);content:"";display:block;pointer-events:none;position:absolute;top:50%;width:100%}.Status .mentions,.Status .reply-to{position:relative;white-space:nowrap}.Status .mentions-text,.Status .reply-to-text{color:var(--faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.Status .mentions-line{display:inline}.Status .replies{display:flex;flex-wrap:wrap;font-size:.85em;line-height:1.3;margin-top:.25em}.Status .replies>*{margin-right:.4em}.Status .reply-link{height:17px}.Status .repeat-info{padding:.4em var(--status-margin,.75em)}.Status .repeat-info .repeat-icon{color:#0fa00f;color:var(--cGreen,#0fa00f)}.Status .repeater-avatar{border-radius:var(--avatarAltRadius,10px);height:20px;margin-left:28px;width:20px}.Status .repeater-name{margin-right:0;text-overflow:ellipsis}.Status .repeater-name .emoji{height:14px;-o-object-fit:contain;object-fit:contain;vertical-align:middle;width:14px}.Status .status-fadein{animation-duration:.4s;animation-name:fadein}@keyframes fadein{0%{opacity:0}to{opacity:1}}.Status .status-actions{display:flex;margin-top:var(--status-margin,.75em);position:relative;width:100%}.Status .status-actions>*{flex:1;max-width:4em}.Status .muted{display:flex;flex-wrap:nowrap;height:1.2em;line-height:1.2em;overflow:hidden;padding:.25em .6em;text-overflow:ellipsis}.Status .muted .mute-thread,.Status .muted .mute-words,.Status .muted .status-username{word-wrap:normal;white-space:nowrap;word-break:normal}.Status .muted .mute-words,.Status .muted .status-username{overflow:hidden;text-overflow:ellipsis}.Status .muted .status-username{flex:0 1 auto;font-size:smaller;font-weight:400;margin-right:.2em}.Status .muted .mute-thread{flex:0 0 auto}.Status .muted .mute-words{flex:1 0 5em;margin-left:.2em}.Status .muted .mute-words:before{content:" "}.Status .muted .unmute{display:block;flex:0 0 auto;margin-left:auto}.Status .reply-form{padding-bottom:0;padding-top:0}.Status .reply-body{flex:1}.Status .favs-repeated-users{margin-top:var(--status-margin,.75em)}.Status .stats{display:flex;line-height:1em;width:100%}.Status .avatar-row{align-items:center;display:flex;flex:1;overflow:hidden;position:relative}.Status .avatar-row:before{background-color:var(--faint,hsla(240,1%,73%,.5));content:"";height:100%;left:0;position:absolute;width:1px}.Status .stat-count{margin-right:var(--status-margin,.75em);-webkit-user-select:none;-moz-user-select:none;user-select:none}.Status .stat-count .stat-title{color:var(--faint,hsla(240,1%,73%,.5));font-size:.85em;position:relative;text-transform:uppercase}.Status .stat-count .stat-number{font-size:1.1em;font-weight:bolder;line-height:1em}.Status .stat-count:hover .stat-title{text-decoration:underline}@media (max-width:800px){.Status .repeater-avatar{margin-left:20px}.Status .post-avatar{height:40px;width:40px}.Status .post-avatar.-compact{height:32px;width:32px}}.Report .report-content,.Report .report-state{margin:.5em 0 1em}.Report .reported-status{border:1px solid hsla(240,1%,73%,.5);border-color:var(--faint,hsla(240,1%,73%,.5));border-radius:4px;border-radius:var(--inputRadius,4px);color:#b9b9ba;color:var(--text,#b9b9ba);display:block;margin:.5em 0;padding:.5em}.Report .reported-status .status-content{pointer-events:none}.Report .reported-status .reported-status-heading{display:flex;justify-content:space-between;margin-bottom:.2em;width:100%}.Report .reported-status .reported-status-name{font-weight:700}.Report .note{margin-bottom:.5em;width:100%}.Notification{word-wrap:break-word;--emoji-size:14px;border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);word-break:break-word}.Notification:hover{--_still-image-img-visibility:visible;--_still-image-canvas-visibility:hidden;--_still-image-label-visibility:hidden}.Notification.-muted{display:flex;flex-wrap:nowrap;height:1.2em;line-height:1.2em;overflow:hidden;padding:.25em .6em;text-overflow:ellipsis}.Notification.-muted .mute-thread,.Notification.-muted .mute-words,.Notification.-muted .status-username{word-wrap:normal;white-space:nowrap;word-break:normal}.Notification.-muted .mute-words,.Notification.-muted .status-username{overflow:hidden;text-overflow:ellipsis}.Notification.-muted .status-username{flex:0 1 auto;font-size:smaller;font-weight:400;margin-right:.2em}.Notification.-muted .mute-thread{flex:0 0 auto}.Notification.-muted .mute-words{flex:1 0 5em;margin-left:.2em}.Notification.-muted .mute-words:before{content:" "}.Notification.-muted .unmute{display:block;flex:0 0 auto;margin-left:auto}.Notification .type-icon{margin:0 .1em}.Notification.-type--repeat .type-icon{color:#0fa00f;color:var(--cGreen,#0fa00f)}.Notification.-type--follow .type-icon,.Notification.-type--follow-request .type-icon{color:#0095ff;color:var(--cBlue,#0095ff)}.Notification.-type--like .type-icon{color:orange;color:var(--cOrange,orange)}.Notification.-type--move .type-icon{color:#0095ff;color:var(--cBlue,#0095ff)}.Notifications:not(.minimal){padding-bottom:15em}.Notifications .loadmore-error{color:#b9b9ba;color:var(--text,#b9b9ba)}.Notifications .notification{position:relative}.Notifications .notification .notification-overlay{bottom:0;left:0;pointer-events:none;position:absolute;right:0;top:0}.Notifications .notification.unseen .notification-overlay{background-image:linear-gradient(135deg,var(--badgeNotification,red) 4px,transparent 10px)}.notification{box-sizing:border-box}.notification:hover .animated.Avatar canvas{display:none}.notification:hover .animated.Avatar img{visibility:visible}.notification:last-child .Notification{border-bottom:none}.notification .non-mention{display:flex;flex:1;flex-wrap:nowrap;min-width:0;padding:.6em}.notification .non-mention .avatar-container{height:32px;width:32px}.notification .non-mention .faint{--link:var(--faintLink);--text:var(--faint)}.notification .follow-request-accept:hover{color:#b9b9ba;color:var(--text,#b9b9ba)}.notification .follow-request-reject:hover{color:red;color:var(--cRed,red)}.notification .follow-text,.notification .move-text{display:flex;justify-content:space-between;overflow-wrap:break-word;padding:.5em 0}.notification .follow-text .follow-name,.notification .move-text .follow-name{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.notification .Status{flex:1}.notification time{white-space:nowrap}.notification .notification-right{flex:1;min-width:0;padding-left:.8em}.notification .notification-right .timeago{min-width:3em;text-align:right}.notification .notification-right .timeago-link{margin-right:.2em}.notification .notification-right .expand-icon .svg-inline--fa{margin-left:.25em}.notification .emoji-reaction-emoji{font-size:1.3em}.notification .notification-details{word-wrap:break-word;display:flex;flex:1 1 0;flex-wrap:nowrap;justify-content:space-between;line-height:var(--post-line-height);min-width:0;overflow:hidden;position:relative;width:100%}.notification .notification-details .name-and-action{flex:1;overflow:hidden;text-overflow:ellipsis}.notification .notification-details .username{font-weight:bolder;max-width:100%;text-overflow:ellipsis;white-space:nowrap}.notification .notification-details .timeago{margin-right:.2em}.notification .notification-details .status-content{margin:0;max-height:300px}.notification .notification-details h1{font-size:1em;line-height:1.5;margin:0 0 .3em;padding:0;word-break:break-all}.notification .notification-details h1 small{font-weight:lighter}.notification .notification-details p{margin:0 0 .3em}.MobileNav{z-index:var(--ZI_navbar)}.MobileNav .mobile-nav{box-sizing:border-box;display:grid;grid-template-columns:2fr auto;grid-template-rows:50px;line-height:var(--navbar-height);width:100%}.MobileNav .mobile-nav a{color:var(--topBarLink,#d8a070)}.MobileNav .mobile-inner-nav{align-items:center;display:flex;width:100%}.MobileNav .mobile-nav-button{cursor:pointer;display:inline-block;padding:0 1em;position:relative;text-align:center}.MobileNav .site-name{display:inline-block;padding:0 .3em}.MobileNav .item{display:flex}.MobileNav .alert-dot{background-color:red;background-color:var(--badgeNotification,red);border-radius:100%;height:8px;left:calc(50% - 4px);margin-left:6px;margin-top:-6px;position:absolute;top:calc(50% - 4px);width:8px}.MobileNav .mobile-notifications-drawer{-webkit-overflow-scrolling:touch;box-shadow:1px 1px 4px rgba(0,0,0,.6);box-shadow:var(--panelShadow);height:100vh;left:0;overflow-x:hidden;position:fixed;top:0;transform:translateX(0);transition-duration:.25s;transition-property:transform;width:100%;z-index:var(--ZI_navbar)}.MobileNav .mobile-notifications-drawer.-closed{box-shadow:none;transform:translateX(100%)}.MobileNav .mobile-notifications-header{align-items:center;background-color:#182230;background-color:var(--topBar,#182230);box-shadow:0 0 4px rgba(0,0,0,.6);box-shadow:var(--topBarShadow);color:var(--topBarText);display:flex;height:50px;justify-content:space-between;line-height:50px;position:absolute;width:100%;z-index:calc(var(--ZI_navbar) + 100)}.MobileNav .mobile-notifications-header .spacer{flex:1}.MobileNav .mobile-notifications-header .title{font-size:1.3em;margin-left:.6em}.MobileNav .pins{flex:1}.MobileNav .pins .pinned-item{flex-grow:1}.MobileNav .mobile-notifications{background-color:#121a24;background-color:var(--bg,#121a24);color:#b9b9ba;color:var(--text,#b9b9ba);height:calc(100vh - var(--navbar-height));margin-top:50px;overflow-x:hidden;overflow-y:scroll;width:100vw}.MobileNav .mobile-notifications .notifications{border-radius:0;box-shadow:none;padding:0}.MobileNav .mobile-notifications .notifications .panel{border-radius:0;box-shadow:none;margin:0}.MobileNav .mobile-notifications .notifications .panel:after{border-radius:0}.MobileNav .mobile-notifications .notifications .panel .panel-heading{border-radius:0;box-shadow:none}.MobileNav .confirm-modal.dark-overlay:before{z-index:3000}.MobileNav .confirm-modal.dark-overlay .dialog-modal.panel{z-index:3001}.SearchBar{align-items:baseline;display:inline-flex;justify-content:flex-end;vertical-align:baseline}.SearchBar.-expanded{width:100%}.SearchBar .search-bar-input,.SearchBar .search-button{height:29px}.SearchBar .search-bar-input{flex:1 0 auto}.SearchBar .cancel-search{height:50px}.SearchBar .cancel-icon{color:#b9b9ba;color:var(--btnTopBarText,#b9b9ba)}.DesktopNav{width:100%;z-index:var(--ZI_navbar)}.DesktopNav input{color:var(--inputTopbarText,var(--inputText))}.DesktopNav a{color:var(--topBarLink,#d8a070)}.DesktopNav .inner-nav{box-sizing:border-box;display:grid;grid-template-areas:"sitename logo actions";grid-template-columns:2fr auto 2fr;grid-template-rows:var(--navbar-height);margin:auto;max-width:980px;padding:0 1.2em}.DesktopNav.-column-stretch .inner-nav{--miniColumn:25rem;--maxiColumn:45rem;--columnGap:1em;max-width:calc(var(--sidebarColumnWidth, var(--miniColumn)) + var(--contentColumnWidth, var(--maxiColumn)) + var(--columnGap))}.DesktopNav.-logoLeft .inner-nav{grid-template-areas:"logo sitename actions";grid-template-columns:auto 2fr 2fr}.DesktopNav.-column-stretch.-wide .inner-nav{max-width:calc(var(--sidebarColumnWidth, var(--miniColumn)) + var(--contentColumnWidth, var(--maxiColumn)) + var(--notifsColumnWidth, var(--miniColumn)) + var(--columnGap))}.DesktopNav .button-default,.DesktopNav .button-default svg{color:#b9b9ba;color:var(--btnTopBarText,#b9b9ba)}.DesktopNav .button-default:active{background-color:#182230;background-color:var(--btnPressedTopBar,#182230);color:#b9b9ba;color:var(--btnPressedTopBarText,#b9b9ba)}.DesktopNav .button-default:disabled{color:#b9b9ba;color:var(--btnDisabledTopBarText,#b9b9ba)}.DesktopNav .button-default.toggled{background-color:#182230;background-color:var(--btnToggledTopBar,#182230);color:#b9b9ba;color:var(--btnToggledTopBarText,#b9b9ba)}.DesktopNav .logo{grid-area:logo;position:relative;transition:opacity;transition-duration:.1s;transition-timing-function:ease-out}@media (min-width:800px){.DesktopNav .logo{opacity:1!important}}.DesktopNav .logo .mask{background-color:#182230;background-color:var(--topBarText,#182230);bottom:0;left:0;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;right:0;top:0}.DesktopNav .logo img{display:inline-block;height:var(--navbar-height)}.DesktopNav .nav-icon{height:100%;margin-left:.2em;text-align:center;width:2em}.DesktopNav .nav-icon .svg-inline--fa{color:#d8a070;color:var(--topBarLink,#d8a070)}.DesktopNav .sitename{grid-area:sitename}.DesktopNav .actions{grid-area:actions}.DesktopNav .item{display:flex;flex:1;flex-wrap:wrap;height:var(--navbar-height);line-height:var(--navbar-height);overflow:hidden}.DesktopNav .item.right{justify-content:flex-end;text-align:right}.DesktopNav .spacer{width:1em}.list-item:not(:last-child){border-bottom:1px solid #222;border-bottom-color:var(--border,#222)}.list-empty-content{padding:10px;text-align:center}.user-reporting-panel{max-height:80vh;max-width:700px;min-height:20vh;width:90vw}.user-reporting-panel .panel-body{border-color:currentcolor #222 #222;border-top:1px solid #222;border-color:var(--border,#222);display:flex;flex-direction:column-reverse;overflow:hidden}.user-reporting-panel-left{box-sizing:border-box;line-height:var(--post-line-height);padding:1.1em .7em .7em}.user-reporting-panel-left>div{margin-bottom:1em}.user-reporting-panel-left>div:last-child{margin-bottom:0}.user-reporting-panel-left p{margin-top:0}.user-reporting-panel-left textarea.form-control{line-height:16px;min-height:44px;overflow:hidden;resize:none;transition:min-height .2s .1s;width:100%}.user-reporting-panel-left .btn{min-width:10em;padding:0 2em}.user-reporting-panel-left .alert{line-height:1.3em;margin:1em 0 0}.user-reporting-panel-right{display:flex;flex-direction:column;overflow-y:auto}.user-reporting-panel-sitem{display:flex;justify-content:space-between}.user-reporting-panel-sitem>.Status{flex:1}.user-reporting-panel-sitem>.checkbox{margin:.75em}@media (min-width:801px){.user-reporting-panel .panel-body{flex-direction:row}.user-reporting-panel-left{border-right:1px solid;border-color:#222;border-color:var(--border,#222);max-width:320px;padding:1.1em;width:50%}.user-reporting-panel-left>div{margin-bottom:2em}.user-reporting-panel-right{flex:1 1 auto;margin-bottom:12px;width:50%}}.modal-view.edit-form-modal-view{align-items:flex-start}.edit-form-modal-panel{flex-shrink:0;margin-bottom:2em;margin-top:25%;max-width:700px;width:100%}@media(orientation:landscape){.edit-form-modal-panel{margin-top:8%}}.edit-form-modal-panel .form-bottom-left{max-width:6.5em}.edit-form-modal-panel .form-bottom-left .emoji-icon{justify-content:right}.modal-view.post-form-modal-view{align-items:flex-start}.post-form-modal-panel{flex-shrink:0;margin-bottom:2em;margin-top:25%;max-width:700px;width:100%}@media(orientation:landscape){.post-form-modal-panel{margin-top:8%}}.modal-view.status-history-modal-view{align-items:flex-start}.status-history-modal-panel{flex-shrink:0;margin-bottom:2em;margin-top:25%;max-width:700px;width:100%}@media(orientation:landscape){.status-history-modal-panel{margin-top:8%}}.global-notice-list{align-items:center;display:flex;flex-direction:column;pointer-events:none;position:fixed;top:calc(var(--navbar-height) + .5em);width:100%;z-index:var(--ZI_navbar_popovers)}.global-notice-list .global-notice{display:flex;line-height:2;margin-bottom:.5em;max-width:calc(100% - 3em);padding-left:1.5em;pointer-events:auto;text-align:center;width:40em}.global-notice-list .global-notice .notice-message{flex:1 1 100%}.global-notice-list .global-error{background-color:var(--alertPopupError,red)}.global-notice-list .global-error,.global-notice-list .global-error .svg-inline--fa{color:var(--alertPopupErrorText,#b9b9ba)}.global-notice-list .global-warning{background-color:var(--alertPopupWarning,orange)}.global-notice-list .global-warning,.global-notice-list .global-warning .svg-inline--fa{color:var(--alertPopupWarningText,#b9b9ba)}.global-notice-list .global-success{background-color:var(--alertPopupSuccess,#0fa00f)}.global-notice-list .global-success,.global-notice-list .global-success .svg-inline--fa{color:var(--alertPopupSuccessText,#b9b9ba)}.global-notice-list .global-info{background-color:var(--alertPopupNeutral,#182230)}.global-notice-list .global-info,.global-notice-list .global-info .svg-inline--fa{color:var(--alertPopupNeutralText,#b9b9ba)}.global-notice-list .close-notice{padding-right:.2em}.global-notice-list .close-notice .svg-inline--fa:hover{opacity:.6}.panel{background-color:#121a24;background-color:var(--bg,#121a24);display:flex;flex-direction:column;position:relative}.panel,.panel:after{border-radius:10px;border-radius:var(--panelRadius,10px)}.panel:after{bottom:0;box-shadow:1px 1px 4px rgba(0,0,0,.6);box-shadow:var(--panelShadow);content:"";left:0;pointer-events:none;position:absolute;right:0;top:0;z-index:5}.panel-body{padding:var(--panel-body-padding,0)}.panel-body:empty:before{content:"¯\\_(ツ)_/¯";display:block;margin:1em;text-align:center}.panel-body>p{line-height:1.3;margin:0;padding:1em}.panel-footer,.panel-heading{--panel-heading-height-padding:0.6em;--__panel-heading-gap:0.5em;--__panel-heading-height:3.2em;--__panel-heading-height-inner:calc(var(--__panel-heading-height) - var(--panel-heading-height-padding, 0)*2);grid-column-gap:var(--__panel-heading-gap);background-size:cover;box-sizing:border-box;display:grid;flex:none;grid-auto-columns:auto;grid-auto-flow:column;grid-template-columns:minmax(50%,1fr);height:var(--__panel-heading-height);line-height:var(--__panel-heading-height-inner);padding:var(--panel-heading-height-padding);position:relative;z-index:4}.panel-footer.-flexible-height,.panel-heading.-flexible-height{--__panel-heading-height:auto}.panel-footer.-flexible-height:after,.panel-footer.-flexible-height:before,.panel-heading.-flexible-height:after,.panel-heading.-flexible-height:before{display:none}.panel-footer.-stub,.panel-footer.-stub:after,.panel-heading.-stub,.panel-heading.-stub:after{border-radius:10px;border-radius:var(--panelRadius,10px)}.panel-footer.-sticky,.panel-heading.-sticky{position:sticky;top:var(--navbar-height)}.panel-footer:after,.panel-footer:before,.panel-heading:after,.panel-heading:before{bottom:0;content:"";left:0;pointer-events:none;position:absolute;right:0;top:0}.panel-footer .title,.panel-heading .title{font-size:1.3em}.panel-footer .alert,.panel-heading .alert{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.panel-footer:not(.-flexible-height)>.alert,.panel-footer:not(.-flexible-height)>.button-default,.panel-heading:not(.-flexible-height)>.alert,.panel-heading:not(.-flexible-height)>.button-default{align-self:stretch;box-sizing:border-box;height:var(--__panel-heading-height-inner);margin:0;min-height:0;min-width:1px;padding-bottom:0;padding-top:0}.panel-heading{align-items:start;background-color:#121a24;background-color:var(--bg,#121a24);border-width:0 0 1px;color:var(--panelText)}.panel-heading,.panel-heading:after{border-radius:10px 10px 0 0;border-radius:var(--panelRadius,10px) var(--panelRadius,10px) 0 0}.panel-heading:after{background-color:#182230;background-color:var(--panel,#182230);box-shadow:var(--panelHeaderShadow);z-index:-2}.panel-heading .-link,.panel-heading a{color:#d8a070;color:var(--panelLink,#d8a070)}.panel-heading .button-unstyled:hover .iconLetter,.panel-heading .button-unstyled:hover .svg-inline--fa,.panel-heading .button-unstyled:hover i[class*=icon-],.panel-heading a:hover .iconLetter,.panel-heading a:hover .svg-inline--fa,.panel-heading a:hover i[class*=icon-]{color:var(--panelText)}.panel-heading .faint{background-color:transparent;color:hsla(240,1%,73%,.5);color:var(--panelFaint,hsla(240,1%,73%,.5))}.panel-heading .faint-link{color:hsla(240,1%,73%,.5);color:var(--faintLink,hsla(240,1%,73%,.5))}.panel-heading:not(.-flexible-height)>.button-default{flex-shrink:0}.panel-heading:not(.-flexible-height)>.button-default,.panel-heading:not(.-flexible-height)>.button-default i[class*=icon-]{color:#b9b9ba;color:var(--btnPanelText,#b9b9ba)}.panel-heading:not(.-flexible-height)>.button-default:active{background-color:#182230;background-color:var(--btnPressedPanel,#182230);color:#b9b9ba;color:var(--btnPressedPanelText,#b9b9ba)}.panel-heading:not(.-flexible-height)>.button-default:disabled{color:#b9b9ba;color:var(--btnDisabledPanelText,#b9b9ba)}.panel-heading:not(.-flexible-height)>.button-default.toggled{color:#b9b9ba;color:var(--btnToggledPanelText,#b9b9ba)}.panel-heading .rightside-button{align-self:stretch;height:var(--__panel-heading-height);margin:calc(var(--panel-heading-height-padding)*-1) 0;margin-right:calc(var(--__panel-heading-gap)*-1);text-align:center;width:var(--__panel-heading-height)}.panel-heading .rightside-button>button{box-sizing:border-box;height:100%;padding:calc(var(--panel-heading-height-padding)*1) 0;text-align:center;width:100%}.panel-heading .rightside-button>button svg{font-size:1.2em}.panel-heading .rightside-icon{align-self:stretch;margin-right:calc(var(--__panel-heading-gap)*-1);text-align:center;width:var(--__panel-heading-height)}.panel-heading .rightside-icon svg{font-size:1.2em}.panel-footer{align-items:center;border-color:var(--border,#222);border-radius:0 0 10px 10px;border-radius:0 0 var(--panelRadius,10px) var(--panelRadius,10px);border-style:solid;border-width:1px 0 0}:root{--navbar-height:3.5rem;--post-line-height:1.4;--ZI_media_modal:9000;--ZI_modals_popovers:8500;--ZI_modals:8000;--ZI_navbar_popovers:7500;--ZI_navbar:7000;--ZI_popovers:6000}html{font-size:14px}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#b9b9ba;color:var(--text,#b9b9ba);font-family:sans-serif;font-family:var(--interfaceFont,sans-serif);margin:0;overflow-x:clip;overflow-y:scroll;overscroll-behavior-y:none}body.hidden{display:none}@media(any-pointer:fine){*{scrollbar-color:var(--btn) transparent}::-webkit-scrollbar{background:transparent}::-webkit-scrollbar-button,::-webkit-scrollbar-thumb{background-color:var(--btn);border-radius:var(--btnRadius);box-shadow:var(--buttonShadow)}::-webkit-scrollbar-button{--___bgPadding:2px;background-repeat:no-repeat,no-repeat;color:var(--btnText)}::-webkit-scrollbar-button:horizontal{background-size:50% calc(50% - var(--___bgPadding)),50% calc(50% - var(--___bgPadding))}::-webkit-scrollbar-button:horizontal:increment{background-image:linear-gradient(45deg,var(--btnText) 50%,transparent 51%),linear-gradient(-45deg,transparent 50%,var(--btnText) 51%);background-position:top var(--___bgPadding) left 50%,right 50% bottom var(--___bgPadding)}::-webkit-scrollbar-button:horizontal:decrement{background-image:linear-gradient(45deg,transparent 50%,var(--btnText) 51%),linear-gradient(-45deg,var(--btnText) 50%,transparent 51%);background-position:bottom var(--___bgPadding) right 50%,left 50% top var(--___bgPadding)}::-webkit-scrollbar-button:vertical{background-size:calc(50% - var(--___bgPadding)) 50%,calc(50% - var(--___bgPadding)) 50%}::-webkit-scrollbar-button:vertical:increment{background-image:linear-gradient(-45deg,transparent 50%,var(--btnText) 51%),linear-gradient(45deg,transparent 50%,var(--btnText) 51%);background-position:right var(--___bgPadding) top 50%,left var(--___bgPadding) top 50%}::-webkit-scrollbar-button:vertical:decrement{background-image:linear-gradient(-45deg,var(--btnText) 50%,transparent 51%),linear-gradient(45deg,var(--btnText) 50%,transparent 51%);background-position:left var(--___bgPadding) top 50%,right var(--___bgPadding) top 50%}html{background:var(--wallpaper);scrollbar-color:var(--selectedMenu) var(--wallpaper)}}a{color:#d8a070;color:var(--link,#d8a070);text-decoration:none}h4{margin:0}.iconLetter{display:inline-block;font-weight:1000;text-align:center}.iconLetter,.svg-inline--fa,i[class*=icon-]{color:#666;color:var(--icon,#666)}.button-unstyled:hover>.iconLetter,.button-unstyled:hover>.svg-inline--fa,.button-unstyled:hover>i[class*=icon-],a:hover>.iconLetter,a:hover>.svg-inline--fa,a:hover>i[class*=icon-]{color:var(--text)}nav{background-color:#182230;background-color:var(--topBar,#182230);box-shadow:0 0 4px rgba(0,0,0,.6);box-shadow:var(--topBarShadow);box-sizing:border-box;color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5));height:var(--navbar-height);position:fixed;z-index:var(--ZI_navbar)}#sidebar{grid-area:sidebar}#modal{position:absolute;z-index:var(--ZI_modals)}.column.-scrollable{position:sticky;top:var(--navbar-height)}#main-scroller{grid-area:content;position:relative}#notifs-column{grid-area:notifs}.app-bg-wrapper{background-color:var(--wallpaper);background-image:var(--body-background-image);background-position:50%;background-repeat:no-repeat;background-size:cover;height:100%;left:0;position:fixed;right:-20px;top:var(--navbar-height);z-index:-1000}.underlay{background-color:rgba(0,0,0,.15);background-color:var(--underlay,rgba(0,0,0,.15));grid-column:1/span 3;grid-row:1/1;pointer-events:none;z-index:-1000}.app-layout{--miniColumn:25rem;--maxiColumn:45rem;--columnGap:1em;--status-margin:0.75em;--effectiveSidebarColumnWidth:minmax(var(--miniColumn),var(--sidebarColumnWidth,var(--miniColumn)));--effectiveNotifsColumnWidth:minmax(var(--miniColumn),var(--notifsColumnWidth,var(--miniColumn)));--effectiveContentColumnWidth:minmax(var(--miniColumn),var(--contentColumnWidth,var(--maxiColumn)));align-content:flex-start;flex-wrap:wrap;grid-template-areas:"sidebar content";grid-template-columns:var(--effectiveSidebarColumnWidth) var(--effectiveContentColumnWidth);grid-template-rows:1fr;justify-content:center;margin:0 auto;min-height:100vh;overflow-x:clip;position:relative}.app-layout,.app-layout .column{box-sizing:border-box;display:grid}.app-layout .column{--___columnMargin:var(--columnGap);align-content:start;grid-row:1/1;grid-template-columns:100%;margin:0 calc(var(--___columnMargin)/2);padding:calc(var(--___columnMargin)) 0;row-gap:var(--___columnMargin)}.app-layout .column:not(.-scrollable){margin-top:var(--navbar-height)}.app-layout .column:hover{z-index:2}.app-layout .column.-full-height{margin-bottom:0;padding-bottom:0;padding-top:0}.app-layout .column.-scrollable{--___paddingIncrease:calc(var(--columnGap)/2);margin-left:calc(var(--___paddingIncrease)*-1);max-height:calc(100vh - var(--navbar-height));overflow-x:hidden;overflow-y:auto;padding-left:calc(var(--___paddingIncrease) + var(--___columnMargin)/2);position:sticky;top:var(--navbar-height)}@supports(scrollbar-width:none) or (-webkit-text-fill-color:initial){.app-layout .column.-scrollable:not(.-show-scrollbar){margin-right:calc(var(--___paddingIncrease)*-1);padding-right:calc(var(--___paddingIncrease) + var(--___columnMargin)/2);scrollbar-width:none}.app-layout .column.-scrollable:not(.-show-scrollbar)::-webkit-scrollbar{display:block;width:0}}.app-layout .column.-scrollable .panel-heading.-sticky{top:calc(var(--columnGap)/-1)}.app-layout.-has-new-post-button .column{padding-bottom:10rem}.app-layout.-no-sticky-headers .column .panel-heading.-sticky{position:relative;top:0}.app-layout .column-inner{align-content:start;box-sizing:border-box;display:grid;grid-template-columns:100%;row-gap:1em}.app-layout.-reverse:not(.-wide,.-mobile){grid-template-areas:"content sidebar";grid-template-columns:var(--effectiveContentColumnWidth) var(--effectiveSidebarColumnWidth)}.app-layout.-wide{grid-template-areas:"sidebar content notifs";grid-template-columns:var(--effectiveSidebarColumnWidth) var(--effectiveContentColumnWidth) var(--effectiveNotifsColumnWidth)}.app-layout.-wide.-reverse{grid-template-areas:"notifs content sidebar";grid-template-columns:var(--effectiveNotifsColumnWidth) var(--effectiveContentColumnWidth) var(--effectiveSidebarColumnWidth)}.app-layout.-mobile{grid-template-areas:"content";grid-template-columns:100vw;padding:0}.app-layout.-mobile .column{margin:var(--navbar-height) 0 0 0;padding-top:0}.app-layout.-mobile .panel,.app-layout.-mobile .panel-heading,.app-layout.-mobile .panel-heading:after,.app-layout.-mobile .panel-heading:before,.app-layout.-mobile .panel:after{border-top-left-radius:0;border-top-right-radius:0}.app-layout.-mobile #notifs-column,.app-layout.-mobile #sidebar,.app-layout.-normal #notifs-column{display:none}.text-center{text-align:center}.button-default{background-color:#182230;background-color:var(--btn,#182230);border:none;border-radius:4px;border-radius:var(--btnRadius,4px);box-shadow:0 0 2px 0 #000,inset 0 1px 0 0 hsla(0,0%,100%,.2),inset 0 -1px 0 0 rgba(0,0,0,.2);box-shadow:var(--buttonShadow);color:#b9b9ba;color:var(--btnText,#b9b9ba);cursor:pointer;font-family:sans-serif;font-family:var(--interfaceFont,sans-serif);font-size:1em;-webkit-user-select:none;-moz-user-select:none;user-select:none}.button-default.-sublime{background:transparent}.button-default .svg-inline--fa,.button-default i[class*=icon-]{color:#b9b9ba;color:var(--btnText,#b9b9ba)}.button-default::-moz-focus-inner{border:none}.button-default:hover{box-shadow:0 0 4px hsla(0,0%,100%,.3);box-shadow:var(--buttonHoverShadow)}.button-default:active{background-color:#182230;background-color:var(--btnPressed,#182230);box-shadow:0 0 4px 0 hsla(0,0%,100%,.3),inset 0 1px 0 0 rgba(0,0,0,.2),inset 0 -1px 0 0 hsla(0,0%,100%,.2);box-shadow:var(--buttonPressedShadow)}.button-default:active,.button-default:active i,.button-default:active svg{color:#b9b9ba;color:var(--btnPressedText,#b9b9ba)}.button-default:disabled{background-color:#182230;background-color:var(--btnDisabled,#182230);cursor:not-allowed}.button-default:disabled,.button-default:disabled i,.button-default:disabled svg{color:#b9b9ba;color:var(--btnDisabledText,#b9b9ba)}.button-default.toggled{background-color:#182230;background-color:var(--btnToggled,#182230);box-shadow:0 0 4px 0 hsla(0,0%,100%,.3),inset 0 1px 0 0 rgba(0,0,0,.2),inset 0 -1px 0 0 hsla(0,0%,100%,.2);box-shadow:var(--buttonPressedShadow)}.button-default.toggled,.button-default.toggled i,.button-default.toggled svg{color:#b9b9ba;color:var(--btnToggledText,#b9b9ba)}.button-default.danger{background-color:rgba(211,16,20,.5);background-color:var(--alertError,rgba(211,16,20,.5));color:#b9b9ba;color:var(--alertErrorPanelText,#b9b9ba)}.button-unstyled{background:none;border:none;box-sizing:content-box;color:inherit;cursor:pointer;display:inline;font-family:inherit;font-size:100%;line-height:unset;outline:none;padding:0;text-align:initial}.button-unstyled.-link{color:#d8a070;color:var(--link,#d8a070)}.button-unstyled.-fullwidth{width:100%}.button-unstyled.-hover-highlight:hover svg{color:#b9b9ba;color:var(--lightText,#b9b9ba)}.input,input,textarea{--_padding:0.5em;background-color:#182230;background-color:var(--input,#182230);border:none;border-radius:4px;border-radius:var(--inputRadius,4px);box-shadow:inset 0 1px 0 0 rgba(0,0,0,.2),inset 0 -1px 0 0 hsla(0,0%,100%,.2),inset 0 0 2px 0 #000;box-shadow:var(--inputShadow);box-sizing:border-box;color:#b9b9ba;color:var(--inputText,#b9b9ba);display:inline-block;font-family:sans-serif;font-family:var(--inputFont,sans-serif);font-size:1em;-webkit-hyphens:none;hyphens:none;line-height:2;margin:0;padding:0 var(--_padding);position:relative}.input.unstyled,input.unstyled,textarea.unstyled{background:none;border-radius:0;box-shadow:none;height:unset}.input.disabled,.input:disabled,.input[disabled=disabled],input.disabled,input:disabled,input[disabled=disabled],textarea.disabled,textarea:disabled,textarea[disabled=disabled]{cursor:not-allowed;opacity:.5}.input[type=range],input[type=range],textarea[type=range]{background:none;border:none;box-shadow:none;flex:1;margin:0}.input[type=radio],input[type=radio],textarea[type=radio]{display:none}.input[type=radio]:checked+label:before,input[type=radio]:checked+label:before,textarea[type=radio]:checked+label:before{background-color:var(--accent,#d8a070);box-shadow:inset 0 0 2px #000,inset 0 0 0 4px #182230;box-shadow:var(--inputShadow),0 0 0 4px var(--fg,#182230) inset}.input[type=radio]:disabled,.input[type=radio]:disabled+label,.input[type=radio]:disabled+label:before,input[type=radio]:disabled,input[type=radio]:disabled+label,input[type=radio]:disabled+label:before,textarea[type=radio]:disabled,textarea[type=radio]:disabled+label,textarea[type=radio]:disabled+label:before{opacity:.5}.input[type=radio]+label:before,input[type=radio]+label:before,textarea[type=radio]+label:before{background-color:#182230;background-color:var(--input,#182230);border-radius:100%;box-shadow:inset 0 0 2px #000;box-shadow:var(--inputShadow);box-sizing:border-box;color:transparent;content:"";display:inline-block;flex-shrink:0;font-size:1.1em;height:1.1em;line-height:1.1;margin-right:.5em;overflow:hidden;text-align:center;transition:box-shadow .2s;vertical-align:top;width:1.1em}.input[type=checkbox]:checked+label:before,input[type=checkbox]:checked+label:before,textarea[type=checkbox]:checked+label:before{color:#b9b9ba;color:var(--inputText,#b9b9ba)}.input[type=checkbox]:disabled,.input[type=checkbox]:disabled+label,.input[type=checkbox]:disabled+label:before,input[type=checkbox]:disabled,input[type=checkbox]:disabled+label,input[type=checkbox]:disabled+label:before,textarea[type=checkbox]:disabled,textarea[type=checkbox]:disabled+label,textarea[type=checkbox]:disabled+label:before{opacity:.5}.input[type=checkbox]+label:before,input[type=checkbox]+label:before,textarea[type=checkbox]+label:before{background-color:#182230;background-color:var(--input,#182230);border-radius:2px;border-radius:var(--checkboxRadius,2px);box-shadow:inset 0 0 2px #000;box-shadow:var(--inputShadow);box-sizing:border-box;color:transparent;content:"✓";display:inline-block;flex-shrink:0;font-size:1.1em;height:1.1em;line-height:1.1;margin-right:.5em;overflow:hidden;text-align:center;transition:color .2s;vertical-align:top;width:1.1em}.input.resize-height,input.resize-height,textarea.resize-height{resize:vertical}textarea{line-height:var(--post-line-height);padding:var(--_padding)}option{background-color:#121a24;background-color:var(--bg,#121a24);color:#b9b9ba;color:var(--text,#b9b9ba)}.hide-number-spinner{-webkit-appearance:textfield;-moz-appearance:textfield;appearance:textfield}.hide-number-spinner[type=number]::-webkit-inner-spin-button,.hide-number-spinner[type=number]::-webkit-outer-spin-button{display:none;opacity:0}.btn-block{display:block;width:100%}.btn-group{display:inline-flex;position:relative;vertical-align:middle}.btn-group button{flex:1 1 auto;position:relative}.btn-group button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.fa{color:gray}.mobile-shown{display:none}.badge{border-radius:99px;box-sizing:border-box;display:inline-block;font-size:.9em;font-style:normal;font-weight:400;height:1.3em;line-height:1;max-width:10em;min-width:1.7em;overflow:hidden;padding:.15em;text-align:center;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.badge.badge-notification{background-color:red;background-color:var(--badgeNotification,red);color:#fff;color:var(--badgeNotificationText,#fff)}.alert{border-radius:5px;border-radius:var(--tooltipRadius,5px);margin:0 .35em;padding:0 .25em}.alert.error{background-color:rgba(211,16,20,.5);background-color:var(--alertError,rgba(211,16,20,.5));color:#b9b9ba;color:var(--alertErrorText,#b9b9ba)}.panel-heading .alert.error{color:#b9b9ba;color:var(--alertErrorPanelText,#b9b9ba)}.alert.warning{background-color:rgba(111,111,20,.5);background-color:var(--alertWarning,rgba(111,111,20,.5));color:#b9b9ba;color:var(--alertWarningText,#b9b9ba)}.panel-heading .alert.warning{color:#b9b9ba;color:var(--alertWarningPanelText,#b9b9ba)}.alert.success{background-color:var(--alertSuccess,rgba(111,111,20,.5));color:var(--alertSuccessText,#b9b9ba)}.panel-heading .alert.success{color:var(--alertSuccessPanelText,#b9b9ba)}.faint,.faint-link{color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5))}.faint-link:hover{text-decoration:underline}.visibility-notice{border:1px solid hsla(240,1%,73%,.5);border:1px solid var(--faint,hsla(240,1%,73%,.5));border-radius:4px;border-radius:var(--inputRadius,4px);padding:.5em}.notice-dismissible{padding-right:4rem;position:relative}.notice-dismissible .dismiss{color:inherit;padding:.5em;position:absolute;right:0;top:0}.fa-scale-110.iconLetter,.fa-scale-110.svg-inline--fa{font-size:1.1em}.fa-old-padding-layer,.fa-old-padding.iconLetter,.fa-old-padding.svg-inline--fa{padding:0 .3em}.veryfaint{opacity:.25}.login-hint{text-align:center}@media (min-width:801px){.login-hint{display:none}}.login-hint a{display:inline-block;padding:1em 0;width:100%}.btn.button-default{min-height:2em}.new-status-notification{flex:1;font-size:1.1em;position:relative;z-index:1}@media (max-width:800px){.mobile-hidden{display:none}}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(359deg)}}@keyframes shakeError{0%{transform:translateX(0)}15%{transform:translateX(.375rem)}30%{transform:translateX(-.375rem)}45%{transform:translateX(.375rem)}60%{transform:translateX(-.375rem)}75%{transform:translateX(.375rem)}90%{transform:translateX(-.375rem)}to{transform:translateX(0)}}.fade-enter-active,.fade-leave-active{transition:opacity .3s}.fade-enter-from,.fade-leave-active{opacity:0}.visible-for-screenreader-only{clip:rect(0 0 0 0);display:block;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;visibility:visible;width:1px}.thread-tree-replies{border-left:2px solid var(--border,#222);margin-left:var(--status-margin,.75em)}.thread-tree-replies-hidden{align-items:stretch;display:flex;flex-direction:column;padding:var(--status-margin,.75em)}.Conversation{z-index:1}.Conversation .conversation-dive-to-top-level-box{align-items:stretch;border-bottom:1px solid var(--border,#222);border-radius:0;display:flex;flex-direction:column;padding:var(--status-margin,.75em)}.Conversation .thread-ancestors{border-left:2px solid var(--border,#222);margin-left:var(--status-margin,.75em)}.Conversation .thread-ancestor.-faded .StatusContent{--link:var(--faintLink);--text:var(--faint);color:var(--text)}.Conversation .thread-ancestor-dive-box{border-bottom:1px solid var(--border,#222);border-radius:0;padding-left:var(--status-margin,.75em)}.Conversation .thread-ancestor-dive-box,.Conversation .thread-ancestor-dive-box-inner{align-items:stretch;display:flex;flex-direction:column}.Conversation .thread-ancestor-dive-box-inner{padding:var(--status-margin,.75em)}.Conversation .conversation-status{border-bottom:1px solid var(--border,#222);border-radius:0}.Conversation .thread-ancestor-has-other-replies .conversation-status,.Conversation .thread-ancestor:last-child .conversation-status,.Conversation .thread-ancestor:last-child .thread-ancestor-dive-box,.Conversation.-expanded .thread-tree .conversation-status,.Conversation:last-child .conversation-status{border-bottom:none}.Conversation .thread-ancestors+.thread-tree>.conversation-status{border-top:1px solid var(--border,#222)}.Conversation.status-fadein.-expanded .thread-body{border-bottom:1px solid var(--border,#222);border-left:4px solid red;border-left-color:var(--cRed,red);border-radius:0 0 10px 10px;border-radius:0 0 var(--panelRadius,10px) var(--panelRadius,10px)}.Conversation.-expanded.status-fadein{margin:calc(var(--status-margin, .75em)/2)}.timeline-menu-popover{border-top-left-radius:0;border-top-right-radius:0;font-size:1rem;margin-top:.6rem;max-width:100vw;min-width:24rem}.timeline-menu-popover ul{list-style:none;margin:0;padding:0}.timeline-menu-popover a{display:block;height:3.5em;line-height:3.5em;padding:0 .65em}.timeline-menu-popover a:hover{color:#d8a070;color:var(--selectedMenuText,#d8a070)}.timeline-menu-popover a.router-link-active,.timeline-menu-popover a:hover{--faint:var(--selectedMenuFaintText,$fallback--faint);--faintLink:var(--selectedMenuFaintLink,$fallback--faint);--lightText:var(--selectedMenuLightText,$fallback--lightText);--icon:var(--selectedMenuIcon,$fallback--icon);background-color:#151e2a;background-color:var(--selectedMenu,#151e2a)}.timeline-menu-popover a.router-link-active{color:#b9b9ba;color:var(--selectedMenuText,#b9b9ba);font-weight:bolder}.timeline-menu-popover a.router-link-active:hover{text-decoration:underline}.timeline-menu-popover a svg{margin-left:-.2em;margin-right:.4em}.timeline-menu-popover li{border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);padding:0}.timeline-menu-popover li:last-child a{border-bottom-left-radius:10px;border-bottom-left-radius:var(--panelRadius,10px);border-bottom-right-radius:10px;border-bottom-right-radius:var(--panelRadius,10px)}.timeline-menu-popover li:last-child{border:none}.TimelineMenu{margin-right:auto;min-width:0}.TimelineMenu .popover-trigger-button{vertical-align:bottom}.TimelineMenu .panel:after{border-top-left-radius:0;border-top-right-radius:0}.TimelineMenu .timeline-menu-title{cursor:pointer;display:flex;margin:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.TimelineMenu .timeline-menu-title .timeline-menu-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.TimelineMenu .timeline-menu-title svg{margin-left:.6em;transition:transform .1s}.TimelineMenu .timeline-menu-title .click-blocker{cursor:default;flex-grow:1}.TimelineMenu.open .timeline-menu-title svg{color:#b9b9ba;color:var(--panelText,#b9b9ba);transform:rotate(180deg)}.TimelineMenu .panel{box-shadow:var(--popoverShadow)}.Timeline .alert-dot{border-radius:100%;height:8px;left:calc(50% - 4px);margin-left:6px;margin-top:-6px;top:calc(50% - 4px);width:8px}.Timeline .alert-badge,.Timeline .alert-dot{background-color:var(--badgeNeutral);position:absolute}.Timeline .alert-badge{border-radius:var(--tooltipRadius);color:var(--badgeNeutralText);font-size:.75em;left:calc(50% - .5em);line-height:1;margin-left:.7em;margin-top:-1em;padding:.2em;text-align:right;top:calc(50% - .4em)}.Timeline .loadmore-button{position:relative}.Timeline.-blocked{cursor:progress}.Timeline .conversation-heading{top:calc(var(--__panel-heading-height)*var(--currentPanelStack, 2));z-index:2}.Timeline.-nonpanel .timeline-heading{line-height:2.75em;padding:0 .5em;text-align:center}.Timeline.-nonpanel .timeline-heading .alert,.Timeline.-nonpanel .timeline-heading .button-default{line-height:2em;width:100%}.tab-switcher{display:flex}.tab-switcher .tab-icon{display:block;margin:.2em auto}.tab-switcher.top-tabs{flex-direction:column}.tab-switcher.top-tabs>.tabs{flex:0 0 auto;flex-direction:row;overflow-x:auto;overflow-y:hidden;padding-top:5px;width:100%}.tab-switcher.top-tabs>.tabs:after,.tab-switcher.top-tabs>.tabs:before{border-bottom:1px solid #222;border-bottom-color:var(--border,#222);content:"";flex:1 1 auto}.tab-switcher.top-tabs>.tabs .tab-wrapper{height:2em}.tab-switcher.top-tabs>.tabs .tab-wrapper:not(.active):after{border-bottom:1px solid #222;border-bottom-color:var(--border,#222);bottom:0;left:0;right:0}.tab-switcher.top-tabs>.tabs .tab{border-bottom-left-radius:0;border-bottom-right-radius:0;margin-bottom:-93px;min-width:1px;padding-bottom:99px;width:100%}.tab-switcher.top-tabs .contents.scrollable-tabs{flex-basis:0}.tab-switcher.side-tabs{flex-direction:row}@media (max-width:800px){.tab-switcher.side-tabs{overflow-x:auto}}.tab-switcher.side-tabs>.contents{flex:1 1 auto}.tab-switcher.side-tabs>.tabs{flex:0 0 auto;flex-direction:column;overflow-x:hidden;overflow-y:auto}.tab-switcher.side-tabs>.tabs:after,.tab-switcher.side-tabs>.tabs:before{border-right:1px solid #222;border-right-color:var(--border,#222);content:"";flex-basis:.5em;flex-shrink:0}.tab-switcher.side-tabs>.tabs:after{flex-grow:1}.tab-switcher.side-tabs>.tabs:before{flex-grow:0}.tab-switcher.side-tabs>.tabs .tab-wrapper{display:flex;flex-direction:column;min-width:10em}@media (max-width:800px){.tab-switcher.side-tabs>.tabs .tab-wrapper{min-width:4em}}.tab-switcher.side-tabs>.tabs .tab-wrapper:not(.active):after{border-right:1px solid #222;border-right-color:var(--border,#222);bottom:0;right:0;top:0}.tab-switcher.side-tabs>.tabs .tab-wrapper:before{border-right:1px solid #222;border-right-color:var(--border,#222);content:"";flex:0 0 6px}.tab-switcher.side-tabs>.tabs .tab-wrapper:last-child .tab{margin-bottom:0}.tab-switcher.side-tabs>.tabs .tab{border-bottom-right-radius:0;border-top-right-radius:0;box-sizing:content-box;flex:1;margin-left:1em;margin-right:-200px;min-width:10em;min-width:1px;padding-left:1em;padding-right:calc(1em + 200px)}@media (max-width:800px){.tab-switcher.side-tabs>.tabs .tab{margin-left:.25em;margin-right:calc(.25em - 200px);padding-left:.25em;padding-right:calc(.25em + 200px)}.tab-switcher.side-tabs>.tabs .tab .text{display:none}}.tab-switcher .contents{flex:1 0 auto;min-height:0}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents .full-height:not(.hidden){display:flex;flex-direction:column;height:100%}.tab-switcher .contents .full-height:not(.hidden)>:not(.mobile-label){flex:1}.tab-switcher .contents.scrollable-tabs{overflow-y:auto}.tab-switcher .tab{padding:6px 1em;position:relative;white-space:nowrap}.tab-switcher .tab:not(.active){z-index:4}.tab-switcher .tab:not(.active):hover{z-index:6}.tab-switcher .tab.active{background:transparent;color:#b9b9ba;color:var(--tabActiveText,#b9b9ba);z-index:5}.tab-switcher .tab img{margin-top:-5px;max-height:26px;vertical-align:top}.tab-switcher .tabs{box-sizing:border-box;display:flex;position:relative}.tab-switcher .tabs:after,.tab-switcher .tabs:before{display:block;flex:1 1 auto}.tab-switcher .tab-wrapper{display:flex;flex:0 0 auto;position:relative}.tab-switcher .tab-wrapper:not(.active):after{content:"";position:absolute;z-index:7}.tab-switcher .mobile-label{border-bottom:1px solid var(--border,#222);margin-bottom:.25em;margin-left:.2em;margin-top:.5em;padding-bottom:.25em;padding-left:.3em}@media (min-width:800px){.tab-switcher .mobile-label{display:none}}.chat-title{--emoji-size:14px;display:flex}.chat-title,.chat-title .username{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-title .username{word-wrap:break-word;display:inline;max-width:100%}.chat-title .avatar-container{align-self:center;line-height:1}.chat-title .titlebar-avatar{border-radius:10px;border-radius:var(--avatarAltRadius,10px);height:1.5em;margin-right:.5em;width:1.5em}.chat-title .titlebar-avatar.animated:before{display:none}.chat-list-item{box-sizing:border-box;cursor:pointer;display:flex;flex-direction:row;height:5em;overflow:hidden;padding:.75em}.chat-list-item :focus{outline:none}.chat-list-item:hover{background-color:var(--selectedPost,#151e2a);box-shadow:0 0 3px 1px rgba(0,0,0,.1)}.chat-list-item .chat-list-item-left{margin-right:1em}.chat-list-item .chat-list-item-center{word-wrap:break-word;box-sizing:border-box;overflow:hidden;width:100%}.chat-list-item .heading{display:inline-flex;justify-content:space-between;line-height:1em;width:100%}.chat-list-item .heading-right{white-space:nowrap}.chat-list-item .name-and-account-name{flex-shrink:1;line-height:var(--post-line-height);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-list-item .chat-preview{color:#b9b9ba;color:var(--faint,#b9b9ba);display:inline-flex;margin:.35em 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%}.chat-list-item a{color:var(--faintLink,#d8a070);pointer-events:none;text-decoration:none}.chat-list-item:hover .animated.avatar canvas{display:none}.chat-list-item:hover .animated.avatar img{visibility:visible}.chat-list-item .Avatar{border-radius:10px;border-radius:var(--avatarAltRadius,10px)}.chat-list-item .chat-preview-body{--emoji-size:1.4em;padding-right:1em}.chat-list-item .time-wrapper{line-height:var(--post-line-height)}.basic-user-card{--emoji-size:14px;display:flex;flex:1 0;margin:0;padding:.6em 1em}.basic-user-card-collapsed-content{flex:1;margin-left:.7em;min-width:0;text-align:left}.basic-user-card-user-name img{height:16px;-o-object-fit:contain;object-fit:contain;vertical-align:middle;width:16px}.basic-user-card-screen-name,.basic-user-card-user-name-value{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.basic-user-card-expanded-content{flex:1;margin-left:.7em;min-width:0}.chat-new .input-wrap{display:flex;margin:.7em .5em}.chat-new .input-wrap input{width:100%}.chat-new .search-icon{margin-right:.3em}.chat-new .member-list{padding-bottom:.7rem}.chat-new .basic-user-card:hover{background-color:var(--selectedPost,#151e2a);cursor:pointer}.chat-new .go-back-button{align-self:start;height:100%;line-height:1;text-align:center;width:var(--__panel-heading-height-inner)}.chat-list{margin-bottom:0;min-height:25em}.emtpy-chat-list-alert{color:#b9b9ba;color:var(--faint,#b9b9ba);display:flex;font-size:1.2em;justify-content:center;padding:3em}.chat-message-wrapper.hovered-message-chain .animated.Avatar canvas{display:none}.chat-message-wrapper.hovered-message-chain .animated.Avatar img{visibility:visible}.chat-message-wrapper .chat-message-menu{opacity:0;position:absolute;top:-.8em;transition:opacity .1s}.chat-message-wrapper .chat-message-menu button{padding-bottom:.2em;padding-top:.2em}.chat-message-wrapper .menu-icon{cursor:pointer}.chat-message-wrapper .menu-icon:hover,.extra-button-popover.open .chat-message-wrapper .menu-icon{color:#b9b9ba;color:var(--text,#b9b9ba)}.chat-message-wrapper .popover{width:12em}.chat-message-wrapper .chat-message{display:flex;padding-bottom:.5em}.chat-message-wrapper .chat-message .status-body:hover{--_still-image-img-visibility:visible;--_still-image-canvas-visibility:hidden;--_still-image-label-visibility:hidden}.chat-message-wrapper .avatar-wrapper{margin-right:.72em;width:32px}.chat-message-wrapper .attachments,.chat-message-wrapper .link-preview{margin-bottom:1em}.chat-message-wrapper .status{border-radius:10px;border-radius:var(--chatMessageRadius,10px);display:flex;padding:.75em}.chat-message-wrapper .created-at{float:right;font-size:.8em;font-style:italic;margin:-1em 0 -.5em;opacity:.8;position:relative}.chat-message-wrapper .without-attachment .message-content .RichContent:after{content:" ";display:inline-block;margin-right:5.4em}.chat-message-wrapper .pending .created-at,.chat-message-wrapper .pending .status-content.media-body{color:var(--faint)}.chat-message-wrapper .error .created-at,.chat-message-wrapper .error .status-content.media-body{color:red;color:var(--badgeNotification,red)}.chat-message-wrapper .chat-message-inner{align-items:flex-start;display:flex;flex-direction:column;max-width:80%;min-width:10em;width:100%}.chat-message-wrapper .outgoing{align-content:end;display:flex;flex-flow:row wrap;justify-content:flex-end}.chat-message-wrapper .outgoing a{color:var(--chatMessageOutgoingLink,#d8a070)}.chat-message-wrapper .outgoing .status{background-color:var(--chatMessageOutgoingBg,#151e2a);border:1px solid var(--chatMessageOutgoingBorder,--lightBg);color:var(--chatMessageOutgoingText,#b9b9ba)}.chat-message-wrapper .outgoing .chat-message-inner{align-items:flex-end}.chat-message-wrapper .outgoing .chat-message-menu{right:.4rem}.chat-message-wrapper .incoming a{color:var(--chatMessageIncomingLink,#d8a070)}.chat-message-wrapper .incoming .status{background-color:var(--chatMessageIncomingBg,#121a24);border:1px solid var(--chatMessageIncomingBorder,--border)}.chat-message-wrapper .incoming .created-at a,.chat-message-wrapper .incoming .status{color:var(--chatMessageIncomingText,#b9b9ba)}.chat-message-wrapper .incoming .chat-message-menu{left:.4rem}.chat-message-wrapper .chat-message-inner.with-media,.chat-message-wrapper .chat-message-inner.with-media .status{width:100%}.chat-message-wrapper .visible{opacity:1}.chat-message-date-separator{color:#b9b9ba;color:var(--faintedText,#b9b9ba);font-size:.9em;margin:1.4em 0;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none}.chat-view{display:flex;height:100%}.chat-view .chat-view-inner{display:flex;height:auto;overflow:visible;width:100%}.chat-view .chat-view-body{background-color:var(--chatBg,#121a24);border-radius:10px 10px 0 0;border-radius:var(--panelRadius,10px) var(--panelRadius,10px) 0 0;box-sizing:border-box;display:flex;flex-direction:column;margin:0;min-height:calc(100vh - var(--navbar-height));overflow:visible;width:100%}.chat-view .chat-view-body:after{border-radius:0}.chat-view .message-list{display:flex;flex-direction:column;height:100%;justify-content:end;padding:0 .8em}.chat-view .footer{background-color:#121a24;background-color:var(--bg,#121a24);bottom:0;position:sticky;z-index:1}.chat-view .chat-view-heading{grid-template-columns:auto minmax(50%,1fr)}.chat-view .go-back-button{align-self:start;height:100%;line-height:1;text-align:center;width:var(--__panel-heading-height-inner)}.chat-view .jump-to-bottom-button{align-items:center;background-color:#182230;background-color:var(--btn,#182230);border-radius:100%;box-shadow:0 1px 1px rgba(0,0,0,.3),0 2px 4px rgba(0,0,0,.3);cursor:pointer;display:flex;height:2.5em;justify-content:center;opacity:0;position:absolute;right:1.3em;top:-3.2em;transition:all .35s;transition-timing-function:cubic-bezier(0,1,.5,1);visibility:hidden;width:2.5em;z-index:10}.chat-view .jump-to-bottom-button.visible{opacity:1;visibility:visible}.chat-view .jump-to-bottom-button i{color:#b9b9ba;color:var(--text,#b9b9ba);font-size:1em}.chat-view .jump-to-bottom-button .unread-message-count{border-radius:50px;font-size:.8em;left:50%;margin-top:-1rem;padding:.1em;position:absolute}.chat-view .jump-to-bottom-button .chat-loading-error{align-items:flex-end;display:flex;height:100%;width:100%}.chat-view .jump-to-bottom-button .chat-loading-error .error{width:100%}.follow-card-content-container{display:flex;flex-flow:row wrap;flex-shrink:0;justify-content:space-between;line-height:1.5em}.follow-card-button{margin-left:1em;margin-top:.5em;padding:0 1.5em}.follow-card-follow-button{margin-left:auto;margin-top:.5em;width:10em}.with-load-more-footer{border-top:1px solid #222;border-top-color:var(--border,#222);padding:10px;text-align:center}.with-load-more-footer .error{font-size:1rem}.with-load-more-footer a{cursor:pointer}.user-profile{--currentPanelStack:1;flex:2;flex-basis:500px}.user-profile .user-birthday{margin:0 .75em .5em}.user-profile .user-profile-fields{margin:0 .5em}.user-profile .user-profile-fields img{max-height:400px;max-width:100%;-o-object-fit:contain;object-fit:contain;vertical-align:middle}.user-profile .user-profile-fields img.emoji{height:18px;width:18px}.user-profile .user-profile-fields .user-profile-field{border:1px solid var(--border,#222);border-radius:4px;border-radius:var(--inputRadius,4px);display:flex;margin:.25em}.user-profile .user-profile-fields .user-profile-field .user-profile-field-name{border-right:1px solid var(--border,#222);color:var(--lightText);flex:0 1 30%;font-weight:500;min-width:120px;text-align:right}.user-profile .user-profile-fields .user-profile-field .user-profile-field-value{color:var(--text);flex:1 1 70%;margin:0 0 0 .25em}.user-profile .user-profile-fields .user-profile-field .user-profile-field-name,.user-profile .user-profile-fields .user-profile-field .user-profile-field-value{box-sizing:border-box;line-height:1.3;overflow:hidden;padding:.5em 1.5em;text-overflow:ellipsis;white-space:nowrap}.user-profile .userlist-placeholder{align-items:middle;display:flex;justify-content:center;padding:2em}.user-profile-placeholder .panel-body{align-items:middle;display:flex;justify-content:center;padding:7em}.search-result-heading{color:hsla(240,1%,73%,.5);color:var(--faint,hsla(240,1%,73%,.5));padding:.75rem;text-align:center}@media (max-width:800px){.search-nav-heading .tab-switcher .tabs .tab-wrapper{display:block;flex:1 1 auto;justify-content:center;text-align:center}}.search-result{border-bottom:1px solid;border-color:#222;border-color:var(--border,#222);box-sizing:border-box}.search-result-footer{background-color:#182230;background-color:var(--panel,#182230);border-color:var(--border,#222);border-style:solid;border-width:1px 0 0;padding:10px}.search-input-container{display:flex;justify-content:center;padding:.8rem}.search-input-container .search-input{box-sizing:border-box;font-size:1rem;line-height:1.125rem;padding:.5rem;width:100%}.search-input-container .search-button{margin-left:.5em}.loading-icon{padding:1em}.trend{align-items:center;display:flex}.trend .hashtag{flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trend .count,.trend .hashtag{color:#b9b9ba;color:var(--text,#b9b9ba)}.trend .count{flex:0 0 auto;font-size:1.5rem;font-weight:500;line-height:2.25rem;text-align:center;width:2rem}.more-statuses-button{height:3.5em;line-height:3.5em}.interface-language-switcher .language-select{margin-right:1em}.registration-form{display:flex;flex-direction:column;margin:.6em}.registration-form .container{display:flex;flex-direction:row}.registration-form .container>*{min-width:0}.registration-form .terms-of-service{flex:0 1 50%;margin:.8em}.registration-form .text-fields{display:flex;flex:1 0;flex-direction:column;margin-top:.6em}.registration-form textarea{min-height:100px;resize:vertical}.registration-form .form-group{display:flex;flex-direction:column;line-height:2;margin-bottom:1em;padding:.3em 0}.registration-form .form-group--error{animation-duration:.6s;animation-name:shakeError;animation-timing-function:ease-in-out}.registration-form .form-group--error .form--label{color:#f04124;color:var(--cRed,#f04124)}.registration-form .form-error{margin-top:-.7em;text-align:left}.registration-form .form-error span{font-size:.85em}.registration-form .form-error ul{list-style:none;margin-top:0;padding:0 0 0 5px}.registration-form .form-error ul li:before{content:"• "}.registration-form form textarea{line-height:16px;resize:vertical}.registration-form .captcha{margin-bottom:.4em;max-width:350px}.registration-form .btn{height:2em;margin-top:.6em}.registration-form .error{text-align:center}@media (max-width:800px){.registration-form .container{flex-direction:column-reverse}}.password-reset-form{align-items:center;display:flex;flex-direction:column;margin:.6em}.password-reset-form .container{display:flex;flex:1 0;flex-direction:column;margin-top:.6em;max-width:18rem}.password-reset-form .container>*{min-width:0}.password-reset-form .form-group{display:flex;flex-direction:column;line-height:1.85em;margin-bottom:1em;padding:.3em 0}.password-reset-form .error{animation-duration:.4s;animation-name:shakeError;animation-timing-function:ease-in-out;text-align:center}.password-reset-form .alert{margin:.3em 0 1em;padding:.5em}.password-reset-form .password-reset-required{background-color:var(--alertError,rgba(211,16,20,.5));padding:10px 0}.password-reset-form .notice-dismissible{padding-right:2rem}.password-reset-form .dismiss{cursor:pointer}.follow-request-card-content-container{display:flex;flex-flow:row wrap}.follow-request-card-content-container button{flex:1 1;margin-right:.5em;margin-top:.5em;max-width:12em;min-width:8em}.follow-request-card-content-container button:last-child{margin-right:0}.tos-content{margin:1em}.staff-group{padding-left:1em;padding-top:1em}.staff-group .basic-user-card{padding-left:0}.mrf-section{margin:1em}.mrf-section table{padding-bottom:20px;padding-left:10px;text-align:left;width:100%}.mrf-section table td,.mrf-section table th{max-width:360px;overflow:hidden;vertical-align:text-top;width:180px}.mrf-section table td+td,.mrf-section table th+th{width:auto}.list-card{display:flex}.list-name{flex-grow:1}.button-list-edit,.list-name{color:#d8a070;color:var(--link,#d8a070);margin:0;padding:1em}.button-list-edit:hover,.list-name:hover{--faint:var(--selectedMenuFaintText,$fallback--faint);--faintLink:var(--selectedMenuFaintLink,$fallback--faint);--lightText:var(--selectedMenuLightText,$fallback--lightText);background-color:#151e2a;background-color:var(--selectedMenu,#151e2a);color:#d8a070;color:var(--selectedMenuText,#d8a070)}.Lists .new-list-button{padding:0 .5em}.ListsUserSearch .input-wrap{display:flex;margin:.7em .5em}.ListsUserSearch .input-wrap input{width:100%}.ListsUserSearch .search-icon{margin-right:.3em}.panel-loading{align-items:center;color:#b9b9ba;color:var(--text,#b9b9ba);display:flex;font-size:2em;height:100%;justify-content:center}.panel-loading .loading-text svg{color:#b9b9ba;color:var(--text,#b9b9ba);line-height:0;vertical-align:middle}.ListEdit{--panel-body-padding:0.5em;display:flex;flex-direction:column;height:calc(100vh - var(--navbar-height));overflow:hidden}.ListEdit .list-edit-heading{grid-template-columns:auto minmax(50%,1fr)}.ListEdit .panel-body{display:flex;flex:1;flex-direction:column;overflow:hidden}.ListEdit .list-member-management{flex:1 0 auto}.ListEdit .search-icon{margin-right:.3em}.ListEdit .users-list{overflow-y:auto;padding-bottom:.7rem}.ListEdit .members-list,.ListEdit .search-list{flex-direction:column;min-height:0;overflow:hidden}.ListEdit .go-back-button{align-self:start;height:100%;line-height:1;text-align:center;width:var(--__panel-heading-height-inner)}.ListEdit .btn{margin:0 .5em}.ListEdit .panel-footer{grid-template-columns:minmax(10%,1fr)}.ListEdit .panel-footer .footer-button{min-width:9em}.announcement-editor{align-items:stretch;display:flex;flex-direction:column}.announcement-editor .announcement-metadata{margin-top:.5em}.announcement-editor .post-textarea{box-sizing:content-box;height:10em;overflow:none;resize:vertical}.announcement{border-bottom:1px solid var(--border,#222);border-radius:0;padding:var(--status-margin,.75em)}.announcement .body,.announcement .heading{margin-bottom:var(--status-margin,.75em)}.announcement .footer,.announcement .footer .times{display:flex;flex-direction:column}.announcement .footer .actions{display:flex;flex-direction:row;justify-content:space-evenly}.announcement .footer .actions .btn{flex:1;margin:1em;max-width:10em}.announcements-page .post-form{padding:var(--status-margin,.75em)}.announcements-page .post-form .body,.announcements-page .post-form .heading{margin-bottom:var(--status-margin,.75em)}.announcements-page .post-form .post-button{min-width:10em} -/*# sourceMappingURL=app.48e52505beba5b9ab69b.css.map*/ \ No newline at end of file diff --git a/priv/static/static/css/app.48e52505beba5b9ab69b.css.map b/priv/static/static/css/app.48e52505beba5b9ab69b.css.map deleted file mode 100644 index a87315d2b..000000000 --- a/priv/static/static/css/app.48e52505beba5b9ab69b.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"static/css/app.48e52505beba5b9ab69b.css","mappings":"AACA,YASE,mBAGA,uBACA,uCAPA,SACA,aACA,uBAJA,OAUA,SAAQ,CAJR,cACA,oBATA,eAGA,QAFA,MAFA,wBAaA,CAEA,cACE,oBAGF,6BAEE,gCADA,mBACA,CAGF,iBACE,UAIJ,mCACE,GACE,6BAGF,GACE,iCCrCJ,sBAAsB,iBAAiB,CAAC,yDAAyD,eAAe,CAAC,2DAA2D,eAAe,CAAC,2CAA2C,mBAAW,CAAX,mBAAW,CAAX,YAAY,CAAC,4BAA4B,kBAAY,CAAZ,mBAAY,CAAZ,aAAa,CAAC,oCAAoC,kBAAM,CAAC,6BAAqB,CAArB,qBAAqB,CAA5B,UAAM,CAAN,MAAM,CAAuB,eAAe,CAAC,iBAAiB,CAAC,6DAAqF,MAAM,CAA9B,iBAAiB,CAAC,KAAK,CAAQ,qBAAqB,CAAC,6EAA6E,UAAU,CAAC,+EAA+E,WAAW,CAAC,gFAAgF,UAAU,CAAC,kFAAkF,WAAW,CAAC,kCAA+G,4BAA4B,CAAxC,WAAW,CAAgF,SAAS,CAAC,2EAAxC,aAAa,CAAtF,WAAW,CAAxC,MAAM,CAA8G,eAAe,CAAjD,mBAAmB,CAA7H,iBAAiB,CAAC,KAAK,CAAmB,UAAU,CAArB,UAAkS,CCGlsC,YACE,aACA,sBACA,aAEA,iBACE,eACA,WAGF,sBACE,SAGF,0BAIE,mBAFA,aACA,mBAEA,8BAJA,cAIA,CAGF,wBACE,aACA,sBAEA,iBADA,sBACA,CAGF,yBACE,aAEA,YADA,YACA,CAEA,gCACE,WAGF,2BAGE,aAFA,aACA,aACA,CAIJ,mBAGE,uBADA,0BAEA,sCAHA,iBAGA,CCjDJ,cACE,eAEA,iCACE,aCHF,sBAEE,eADA,qBAGA,iBADA,gBAEA,kBAEA,mCACE,aCDgB,CDEhB,+BEbN,UAKE,oBACA,kBAFF,iBAGE,qBAGE,mBADF,iBAEE,4BAeA,wBDrBW,sCCuBX,CANA,iBDAuB,wCCEvB,8BACA,8BACA,CAQA,sBAFA,iBACA,CAfA,WACA,CAFA,aACA,CAaA,eACA,CAXA,YACA,CAQA,iBACA,CAEA,eACA,CApBF,iBACE,QACA,CAaA,iBACA,CAdA,KACA,CAEA,oBACA,CAQA,kBACA,CATA,WAeA,yEAIA,UAEE,2BAGF,yBDtCc,uCCwCZ,mEAKF,aD5Ca,+BC8CX,yEAIA,aDlDW,gCCiDb,WAGE,gBAIJ,gBACE,CChEJ,wBAGA,oBACE,UAOA,qCACA,+BAFA,4BACA,CAFA,WACA,CAFA,cACA,CAFF,qDAME,kBAsBA,gDAEA,qDACA,yDACA,kDACA,4DACA,2CAVA,wBF3Ba,wCE6Bb,CAjBF,iBFOsB,mCEQpB,CAEA,aF1Be,iCEmCf,wBAtBE,QACA,CAGA,qCACA,8BACA,CATF,UACE,CAGA,MACA,CAIA,oBARA,iBACA,CAGA,OACA,CAJA,KACA,CAGA,SAIA,gBAkBJ,aACE,CACA,aACA,CACA,eACA,gBACA,CALA,eACA,CACA,eACA,CAGA,mBADA,qDAEA,kCAKE,yBACA,yCAJF,QACE,eACA,gBAGA,+BAkBA,6CALA,4BACA,CAHA,WACA,gBACA,CACA,eACA,CAEA,qBACA,CAXA,UACA,CAHA,aACA,CAEA,eACA,CAOA,WACA,CAdF,gBACE,gBACA,CACA,kBACA,CAEA,kBACA,mBACA,CAIA,UAKA,wCAKI,kCADA,mBACA,CAFF,UAGE,0DAMA,iBADF,mBAEE,0EAQF,wDAEA,6DACA,iEACA,qEACA,uDATF,wBFvFgB,oDE0Fd,gBAOA,kFAGE,sDADF,yCAGE,8CAaF,wBFxHS,sCE0HT,CAHA,eACA,CAEA,6BACA,8BACA,CAbF,oBACE,CAKA,gBACA,CAMA,mBARA,eACA,CAHA,cACA,gBACA,CAHA,cACA,CAIA,iBACA,CAPA,qBAaA,0EAGE,YADF,gBAEE,qDAGF,oBACE,iFAGE,YADF,aAEE,2GAON,aF9Ia,6BEiJX,qDAGF,wBFjJgB,oDEmJd,cFrJW,6CEuJX,uDAGF,aF3Ja,qCE6JX,sDAGF,aFhKa,oCEkKX,CCtKN,aAKE,mBADA,oBAFA,cACA,gBAFA,iBAIA,CAEA,oBAGE,SACA,OAHA,kBAIA,QAHA,MAOA,yDAGF,qCALE,YACA,yCAFA,UASA,CAIA,6BACE,uCAOA,6BAIA,iBHhBoB,CGiBpB,uCAJA,WAPA,cAQA,cALA,eAEA,UAHA,cAOA,gBARA,kBAGA,SASA,wDADA,SACA,CAGF,mCACE,aAGF,mCACE,uDAGF,0BACE,qDAGF,gCACE,mBCrDN,cAUE,gDAAkD,CAClD,oDAAsD,CACtD,wDAA0D,CAC1D,yCAA0C,CAR1C,wBJRa,CISb,wCACA,aJNe,CIOf,iCALA,aACA,sBAFA,6BADA,UAY2C,CAE3C,2BAGE,mBAFA,oBAKA,WAxBiC,CAoBjC,uBAKA,gBAFA,cAxBgC,CAuBhC,UAtBiC,CA2BjC,wCAGE,YADA,gBADA,eAIA,yCADA,UACA,CAIJ,uDAGE,mBADA,WACA,CAGF,8BACE,aACA,sBAGF,+BAEE,aADA,aACA,CAGF,uBACE,aACA,qBAGF,uBACE,aAEA,cADA,sBAEA,aAGF,0BAEE,aACA,qBAFA,YAGA,gBAGF,+BAIE,8DAHA,aAKA,cADA,gBACA,CAGF,yDAIE,qBADA,aADA,eAEA,CAEA,mEASE,mBAPA,eAMA,aALA,iBAGA,WA5F+B,CA6F/B,eA7F+B,CA2F/B,cA5F8B,CAwF9B,cAGA,UAKA,CAEA,qFACE,WACA,oBAGF,iFACE,wBAEA,yFACE,aJnGY,CIoGZ,+BAMR,8BACE,cAKA,6DACE,aAEA,cADA,sBAEA,aAEA,2EACE,UACA,oBACA,kBAMJ,4BAEE,cADA,WACA,CAEA,kCACE,WAIJ,4BAGE,aAFA,YAMA,+JACE,CADF,uJACE,CAMF,mBACA,kDAHA,8EAVA,iBAGA,cADA,kBAOA,6GALA,+DASA,CAGE,yCACE,wEAGF,4CACE,wEAKN,2BAEE,mBADA,aAEA,eAEA,qBADA,gBACA,CAEA,iCACE,gBAEA,QAAO,CADP,UACA,CAEA,0CACE,aAKN,0BAME,mBAHA,sBAMA,eALA,aAFA,WA9LoB,CAmMpB,uBAFA,gBAjMoB,CAoMpB,WAPA,UAQA,CAEA,sDAGE,gBADA,eADA,wCAEA,CAGF,uDACE,eACA,gBCjNR,aACE,aACA,sBACA,kBAEA,gCAME,eADA,gBAEA,iBAHA,kBAHA,kBAEA,QADA,KAKA,CAEA,wCACE,aLXW,CKYX,0BAIJ,iCAGE,eAFA,kBACA,UACA,CAEA,sCACE,aAIJ,yCAEE,cAGF,+BACE,mBAGF,6BAKE,SAMA,UAJA,OANA,UAOA,gBANA,oBACA,kBAGA,QAFA,KAOA,CAIA,oCAGE,qBADA,8BADA,OAEA,CAMJ,oBACE,kBAGF,mBAIE,uCAFA,eADA,aAIA,YAFA,iBAEA,CAEA,0BAKE,eAHA,YACA,iBAGA,iBAFA,kBAHA,UAKA,CAEA,8BAEE,YACA,yCAFA,UAEA,CAIJ,0BACE,aACA,sBACA,uBACA,qBAEA,uCACE,gBAGF,sCACE,cACA,gBAIJ,+BAKE,4DAA8D,CAC9D,gEAAkE,CAClE,oEAAsE,CACtE,qDAAsD,CAPtD,wBLxGS,CKyGT,oDACA,4CAKuD,CChH7D,aACE,UAEA,oBACE,6DACA,uBACA,YACA,aNJa,CMKb,sCAGA,uBACA,wCACA,cAGA,WACA,iBARA,SACA,qBAIA,WACA,SAEA,CAGF,+BAGE,SAIA,aNxBa,CMyBb,+BAHA,YAIA,cAEA,oBAVA,kBAGA,UAFA,MAIA,aAIA,SACA,CChCJ,WACE,aACA,sBACA,oBAEA,uBACE,sBAEA,kBADA,iBACA,CAGF,wBAEE,qBADA,aAEA,8BACA,oBAGF,4BACE,WAEA,kCAEE,oBACA,WAIJ,0BAGE,mBADA,YAEA,UAGF,6BAEE,aADA,gBAEA,WAGF,sBAEE,aADA,kBACA,CAEA,wCACE,oBAIJ,wBACE,aAEA,uCAEE,iBADA,SACA,CCvDN,OACE,qBAGA,kBAOA,0CARA,YADA,UAgBE,CAPF,oBAIE,mBAEA,qBACA,kBAJA,aAEA,sBAEA,CAGF,cACE,MAGF,cAKE,iBAHA,WACA,gBAFA,kBAGA,kBACA,CAGF,eACE,aACA,oBCpCJ,YAIE,sBAOA,qBTDiB,CSEjB,gCAHA,kBTiB2B,CShB3B,2CATA,oBACA,sBAIA,YADA,cAFA,iBASA,CAEA,gCACE,cACA,YAEA,gBADA,iBACA,CAGF,mCAEE,aADA,WAEA,iBACA,UAEA,qCACE,OAEA,gBAEA,SAGA,gBAJA,aAFA,kBAKA,uBADA,kBAEA,CAGF,2CAME,0BAFA,SAGA,8BALA,OAGA,cAJA,kBAEA,OAIA,CAIJ,+BACE,OACA,YAGF,qLAME,aAGA,YAFA,uBACA,UACA,CAIA,oCAEE,YADA,UACA,CAMF,8IAKE,kBAFA,YACA,yCAFA,UAGA,CAIJ,6BAEE,qBADA,YACA,CAEA,mCAEE,YADA,UACA,CAIJ,mCAGE,mBAFA,aACA,sBAEA,uBACA,iBAGF,uBAKE,0BAHA,eAEA,sBAHA,kBAKA,mCAHA,oBAGA,CAEA,8BACE,SAIJ,gCACE,aAKA,kBADA,gBAHA,kBACA,QACA,MAGA,UAEA,mDAUE,6BARA,iBTvGoB,CSwGpB,uCAKA,iBAFA,WACA,iBANA,UAGA,kBACA,SAKA,CAEA,mEACE,qBAGF,yEACE,qBAMJ,6DAEE,yCAKF,yDAEE,qCAIJ,8BAKE,aAHA,cADA,kBAGA,kBADA,UAEA,CAEA,kCACE,WAGF,qCACE,OAEA,yCACE,SACA,kBACA,YACA,qCAIJ,oCACE,OACA,WACA,qBAEA,uCACE,eACA,SAMJ,mCACE,QACA,WAGF,4CACE,QACA,WAIJ,sBACE,aAEA,uFAEE,SAIJ,yBAEE,aTnNa,CSoNb,8BAFA,qBAKA,YACA,gBAHA,gBACA,kBAEA,CAEA,yCACE,YAGF,mCAGE,qBAFA,aACA,kBACA,CAEA,iHAEE,SACA,UACA,kBAGF,0DACE,OACA,kBAGF,uDAEE,kBADA,QACA,CAIJ,2BACE,qBACA,eACA,gBACA,uBAGF,6BACE,cAIJ,qBACE,gBAIA,4CACE,oBC3QJ,uBACE,aACA,sBAGF,sBAIE,WAAU,CAFV,SADA,kBAEA,UACA,CAEA,yCAQE,sBAHA,SACA,aACA,mBAJA,OAFA,kBAGA,QAFA,KAMA,CAEA,uDAIE,sBAFA,YACA,YAFA,kBAKA,cAEA,kEACE,SAIJ,+CAKE,cADA,aAEA,yDAJA,YACA,kBAFA,UAKA,CAEA,6DAEE,aADA,QACA,CAKN,2DAEE,YAEA,iGACE,kBAIJ,wCACE,gBAKF,6BAGE,8GACE,CADF,sGACE,CAIF,mBACA,kDARA,gBACA,eAOA,CAIJ,gCAEE,aAAY,CADZ,iBACA,CAGF,mCACE,aAGF,kCACE,aACA,OACA,uBACA,cAEA,yCACE,cC9FN,QACE,4CAA6C,CAC7C,qDAAsD,CACtD,mDAAoD,CACpD,sCAAuC,CAEvC,qBAGA,YAFA,kBACA,UACA,CAEA,iBAGE,kBXUwB,CWTxB,0CAFA,YADA,UAGA,CAGF,gBAIE,iBXCqB,uCWFrB,mCADA,YADA,UXIqB,CWErB,+BACE,qCACA,kCAGF,iCACE,aAGF,yBACE,kBXXsB,CWYtB,0CAGF,6BACE,wBXtCS,CWuCT,mCAIJ,YAEE,YADA,UACA,CAGF,uBAME,6BAEA,mCANA,SAKA,WAHA,aACA,aAJA,kBAEA,OAKA,CC3DJ,aAIE,kBADA,eAFA,kBACA,mBAGA,kBAEA,yCAGE,kBADA,cACA,CAGF,6BACE,0CAEA,aAGA,kBADA,gEADA,sBAFA,WAIA,CAGF,mBAQE,iBANA,qBAKA,YADA,OAMA,iBARA,UASA,aAVA,oBAFA,kBAIA,SAKA,4BAIA,6DALA,mBAEA,SAGA,CAGF,oDAEE,gEAGF,uCAEE,mBAGF,wBACE,mBAKE,kCACE,gBAIJ,iCAEE,6CADA,qCACA,CAGF,sBACE,kBAEA,qBACA,cAGA,QAAO,CALP,WAGA,eACA,mBACA,CAIA,sCACE,6LACE,CAWJ,oCACE,kGAKF,mCACE,iEAKN,gCACE,+BAIJ,sBAEE,iBADA,eAEA,gBC/GF,cACE,qBAEA,qDACE,YAGF,4BAGE,kBAFA,iBACA,kBACA,CCVJ,aAIE,kBADA,qBAFA,kBACA,kBAEA,CCDA,wBAGE,wDADA,kBADA,wBAGA,iBAGF,iBACE,cAGF,uFAKE,0CAGF,eACE,eAGF,0BACE,SAGF,gBACE,gBACA,kBACA,eAGF,gBACE,gBACA,aAGF,gBACE,cACA,eAGF,gBACE,eAOF,sCAHE,oBAMA,CAHF,oBAGE,8BADA,4BACA,CAGF,qCAGE,iBADA,eAGA,yCADA,qBACA,CC7DF,aACE,aACA,sBACA,gBAGF,mBACE,kBAGF,qBAKE,ahBRkB,CgBSlB,+BAJA,aACA,mBAFA,YAGA,iBAEA,CAGF,2BAEE,mBADA,aAEA,mBAEA,sBADA,SACA,CAGF,yBAEE,aAAY,CADZ,WACA,CAGF,mBAKE,wBhB/BgB,CgBgChB,qCACA,kBhBtBoB,CgBuBpB,sCALA,ahBhCa,CgBiCb,8BAHA,YASA,OARA,kBAOA,MAEA,qBAGF,mBAEE,mBADA,YACA,CAGF,YACE,YAGF,cAEE,mBADA,YACA,CAGF,gBACE,gBAGF,wBAEE,kBADA,cACA,CAGF,qBACE,aCxEJ,YACE,aACA,sBAEA,mBACE,8BAA+B,CAGjC,yBACE,gBAGF,uCAKE,qBAHA,uCAKA,oCAHA,yBADA,qBAGA,qBACA,CAGF,qBACE,cACA,kBACA,oBAIA,+BAIE,aADA,gBADA,uBADA,kBAGA,CAIJ,6BAIE,gCAFA,mBACA,qBAEA,WAAU,CAJV,kBAIA,CAEA,mCACE,kBAEA,4CACE,eACA,gBAEA,uBADA,kBACA,CAKN,0BACE,aACA,wBAEA,uCAEE,aACA,kBACA,kBAHA,kBAIA,UAEA,mDAEE,8GACE,CADF,sGACE,CAIF,mBACA,kDAPA,YAOA,CAKN,wHAIE,qBAGA,kBADA,WADA,oBAEA,CAGF,+BAEE,YAEA,kBADA,iBAFA,kBAIA,UAGF,gCAEE,oBAGF,yDAEE,qBAEA,iEACE,cAIJ,uBACE,ajBpGe,CiBqGf,mCAGF,sBACE,kCAGF,qBAIE,iBAAiB,CAHjB,gBACA,kBAEkB,CAElB,6DAEE,kBAGF,2BAIE,cAOA,mBACA,kDAJA,gIAFA,oDACA,gEAFA,sEAFA,cAFA,gBACA,kBAUA,CAGF,kCAEE,WAEA,YACA,iBAJA,aAEA,aAEA,CAGF,sCAOE,YACA,qBAHA,oBACA,QAEA,CAPA,qDACE,aASJ,mCACE,qBCtKN,mBAqDE,qBlB5CiB,CkB6CjB,gCAHA,kBlB1B2B,CkB2B3B,2CALA,alB3Ce,CkB4Cf,0BA7CA,eAFA,aACA,mBAGA,gBADA,eAkDA,CA/CA,+BACE,cAEA,cADA,WACA,CAEA,mCAIE,kBlBSuB,CkBRvB,2CAHA,YACA,qCAFA,UAIA,CAIJ,iCAGE,aACA,sBAFA,YADA,eAGA,CAGF,8BACE,gBAGF,qCAKE,kBAJA,gBAOA,6BANA,gBACA,uBACA,qBAIA,CAGF,+BACE,aC9CJ,eACE,OACA,YCAF,kBACE,kBAEA,+BACE,mBAGF,+BACE,aAGA,aAFA,8BACA,YACA,CAEA,sCACE,WAGF,iCAGE,aAFA,aACA,aACA,CAIJ,oCACE,aACA,OAEA,iBACA,eAFA,iBAEA,CAGF,mCACE,aACA,kBAGF,kCAEE,eADA,OAEA,gEAEA,wCACE,0BAGF,0EAGE,eADA,iBAEA,wBAIJ,qCACE,kBAGF,iCAEE,yBpBzDc,CoB0Dd,uCAFA,iBAEA,CAGF,kCACE,sBACA,oCACA,iBpB7CsB,CoB8CtB,uCAEA,QAAO,CADP,YACA,CAIA,4CACE,yBpBxEY,CoByEZ,uCAIJ,mCAIE,qBAHA,aACA,8BACA,eACA,CAIA,+DACE,aAGF,8DACE,gBAKJ,qCAEE,qBADA,OACA,CAGF,8BAEE,uBADA,OACA,CAGF,6BAEE,sBADA,OACA,CAGF,gGAQE,mBADA,aAFA,OAFA,iBACA,gBAEA,cAEA,CAKE,+wBAGE,apBzHc,CoB0Hd,+BAKF,wQAGE,UpBpIS,CoBqIT,kCAFA,kBAEA,CAEA,4SACE,UpBxIO,CoByIP,kCAMR,yBACE,kBAGF,wCAEE,mBADA,kBAEA,WAEA,0FAGE,gBADA,wCACA,CAGF,+CACE,gBAGF,8CACE,OACA,WAIJ,wCACE,aAGA,sBAFA,kBACA,UACA,CAGF,iCACE,mBAGF,uBACE,aACA,sBACA,YACA,kBAGF,8BACE,aACA,sBAEA,iBADA,uBACA,CAGF,kCAEE,uBAMA,yCACA,6CANA,gBAGA,mEAIA,YANA,6BAMA,CAEA,kDACE,gBAIJ,8BACE,kBAGF,qCAEE,SAGA,cADA,UAHA,kBAEA,OAEA,CAEA,2CACE,SpB1NW,CoB2NX,sBAIJ,mBACE,aACA,eAGF,oBACE,cACA,cAGF,kCAME,mBAKA,wBpB7PW,CoB8PX,mCAGA,0BACA,sCAHA,iBpB1OsB,CoB2OtB,uCALA,apBxPa,CoByPb,0BALA,aADA,cADA,YAIA,uBACA,WAPA,kBACA,UAcA,CCrQJ,eACE,gBAEA,8BAEE,eADA,UACA,CCDF,qBASE,6BARA,SACA,YAGA,OAEA,QAGA,aAIJ,yCAVI,eADA,cAGA,eAEA,KAkBF,CAZF,oBAWE,wBtB1Ba,CsB2Bb,mCAVA,SAGA,iBAFA,gBACA,eAGA,2BACA,YAIA,CAGE,iDACE,kBAIJ,0CAGE,wBtBtCW,CsBuCX,mCAHA,SACA,aAGA,mBAGF,yCAGE,wBtB9CW,CsB+CX,mCACA,0BACA,wCACA,aACA,yBAPA,SACA,YAMA,CAEA,gDAEE,kBADA,UACA,CCxDN,0BACE,YAEA,mCAEE,uBACA,YAKF,wDAEE,eCZF,iCAEE,eACA,eACA,kBAHA,WAGA,CAEA,mDACE,cACA,+BCTN,WACE,aACA,sBAEA,oBAIE,mBAHA,aACA,mBACA,8BAEA,oBAEA,yBACE,eAGF,6BACE,aACA,mBACA,sBAEA,kCACE,iBAKN,sBACE,mBAGF,6BAEE,uCADA,iBACA,CCjCJ,WACE,kBACA,UAEA,iBACE,qCAAsC,CACtC,uCAAwC,CACxC,sCAAuC,CAGzC,0BAME,oBAFA,uBADA,gBAEA,sBAJA,eAOA,kBANA,iBAMA,CAGF,uBACE,qBAEA,kCADA,mCAGA,kBAGF,6BAkBE,kCANA,sBAIA,8EACA,+EAHA,wEACA,yEAVA,SAFA,OAGA,oGACE,CADF,4FACE,CAGF,mBACA,kDAEA,8CAZA,kBAGA,QAFA,MAiBA,WAEA,sCACE,gDAIJ,eAEE,cACA,gBAEA,QAAO,CADP,YAHA,iBAIA,CAEA,iBACE,a1BzDW,C0B0DX,8BAGF,mBAIE,iBADA,eAFA,yCACA,qBAEA,CAIJ,sBAME,mCAAoC,CACpC,qBAAqB,CANrB,2B1BzDoB,C0B0DpB,+CACA,4B1B3DoB,C0B4DpB,+CAGsB,CAGxB,oBAIE,mCAAoC,CACpC,sCAAsC,CAJtC,kB1BnEoB,C0BoEpB,qCAGuC,CAGzC,oBAIE,qCAAsC,CACtC,wCAAwC,CAJxC,iB1BvEsB,C0BwEtB,sCAGyC,CAG3C,qBAGE,qB1B9Fe,C0B+Ff,gCAIJ,WAGE,eAEA,wBAJA,a1BrGoB,C0BsGpB,8BAKE,CAEA,mBACE,kBAIJ,sBAIE,uBADA,aAEA,gBAJA,YACA,kBAGA,CAEA,wBACE,YAGF,wBAEE,aADA,qBACA,CAGF,8BACE,sCAAuC,CACvC,+CAAgD,CAChD,6CAA8C,CAG9C,YACA,qCAFA,UAEA,CAIJ,kBAEE,eADA,iBACA,CAEA,2BASE,mBAHA,gCAIA,iB1B5ImB,C0B6InB,sCANA,SAEA,aACA,uBANA,OAUA,UAXA,kBAGA,QADA,MAUA,4BAEA,+BACE,WAIJ,mDACE,UAIJ,iEAEE,eAGA,eACA,eAFA,kBADA,WAGA,CAEA,qGACE,a1BnLgB,C0BoLhB,+BAIJ,wBAGE,qBADA,gBADA,iBAEA,CAEA,mCACE,iBAGF,0CAEE,cADA,cAGA,gBADA,sBACA,CAGF,kCAKE,a1BjNW,C0BkNX,0BAJA,cAEA,eADA,gBAFA,aAKA,CAGF,mCAIE,wB1B3NS,C0B4NT,6CAHA,a1BvNW,C0BwNX,sCAFA,SAIA,CAIJ,yBAYE,kBAAkB,CAXlB,cAKA,WAIA,gBARA,iBACA,gBACA,uBACA,mBAIA,SAGmB,CAEnB,yEAEE,aAIJ,sBAGE,cAEA,gBADA,iBAFA,gBADA,sBAIA,CAGF,sBAGE,qBADA,aAGA,eADA,iBAHA,mBAIA,CAEA,iCACE,cAEA,iBACA,gBAGF,mCAKE,iBAHA,aADA,cAEA,eACA,kBACA,CAEA,oDAEE,cADA,gBACA,CAGF,qDAGE,cADA,iBADA,aAEA,CAGF,sDAEE,cADA,UACA,CAGF,+JAKE,oBADA,kBADA,kBAEA,CAKN,8BAEE,aACA,mBACA,oBAHA,iBAGA,CAEA,gCACE,sBAEA,eADA,kBACA,CAGF,qCACE,SAIJ,sBACE,sBAIJ,8BACE,aAGF,aAME,a1BrUoB,C0BsUpB,+BANA,aAOA,eAHA,8BAHA,iBACA,qBACA,iBAIA,CAGF,YACE,cAEA,cADA,cACA,CAEA,eACE,cACA,mBACA,iBAIF,cACE,qBAIJ,aACE,aACA,mBCvWF,uBACE,iBACA,WCAF,iBAGE,qBADA,sBAMA,a5BHe,C4BIf,0BARA,aAGA,aACA,kBACA,cACA,UAEA,CAEA,oCACE,eAGF,4BACE,OAGF,4BACE,kBAGF,+BAEE,kBADA,SACA,CAEA,0CACE,mBAIJ,uBAME,qDAAuD,CACvD,yDAA2D,CAC3D,6DAA8D,CAP9D,wB5B1BgB,C4B2BhB,6CACA,a5B9Ba,C4B+Bb,qCAI+D,CAE/D,kCACE,kCAAoC,CAIxC,yBAOE,qDAAuD,CACvD,yDAA2D,CAC3D,6DAA8D,CAP9D,wB5B1CgB,C4B2ChB,6CACA,a5B/Ca,C4BgDb,sCAJA,kBAQ+D,CAE/D,oCACE,kCAAoC,CAGtC,+BACE,0BC/DN,gBACE,aACA,eAEA,YADA,eACA,CAEA,2BAOE,oB7BHa,C6BIb,8CAPA,mBACA,YAEA,kBACA,wBACA,qBAHA,UAKA,CAGF,6BAME,sBAJA,aAKA,YAJA,cAEA,iBAJA,kBAGA,iBAGA,CAEA,sFAEE,SAGF,gDAGE,wBAFA,a7B5BW,C6B6BX,8BACA,CAEA,4HAEE,cCrCN,iBAEE,8BADA,eACA,CAGF,aACE,gBACA,SACA,UAGF,aAGE,uB9BNe,C8BMf,iB9BNe,C8BOf,gCAHA,iBAGA,CAIA,oCAGE,2B9BLkB,C8BMlB,+CAHA,4B9BHkB,C8BIlB,+CAEA,CAGF,mCAGE,8B9BZkB,C8BalB,kDAHA,+B9BVkB,C8BWlB,kDAEA,CAIJ,wBACE,YAGF,8BAEE,iBACA,CAGF,2DAHE,gBAFA,gBAOA,CAGF,gCAEE,wB9B7CgB,C8B8ChB,6CAEA,uB9B9Ce,C8B8Cf,iB9B9Ce,C8B+Cf,gCALA,kBAKA,CAGF,qBACE,wB9B3DW,C8B4DX,mCAGF,6BAGE,kCAAmC,CCrErC,mBACE,iBCDF,iBACE,sBAGF,mBAEE,YADA,UACA,CAGF,eAEE,QAAO,CADP,aACA,CAGF,qBAKE,aAHA,gBAEA,UADA,uBAFA,kBAIA,CAGF,oBAEE,aADA,UAEA,kBCvBJ,gBAEE,YAEA,eAHA,eAEA,0BACA,CAEA,sBACE,UAGF,4BACE,WAKF,4BACE,eAEA,kCACE,ajChBW,CiCiBX,+BACA,kBAGF,mCAGE,mBAFA,aACA,6BACA,CAIJ,2BAGE,gBADA,kBADA,eAEA,CAGF,qCACE,YAGF,4BACE,aACA,kBAIA,+BAGE,iBjC5BmB,CiC6BnB,sCAHA,YAIA,kBACA,iBAJA,UAIA,CAIJ,0BACE,aAEA,mCACE,OACA,YACA,iBACA,YAKF,iCACE,aACA,8BCpEJ,wBACE,GACE,UAGF,GACE,WAIJ,yCAME,gBADA,eAHA,eAQA,CAEA,wFATA,mBAFA,aAGA,sBAKA,YADA,YAEA,uBAHA,UAYE,CAIJ,0DAGE,WACA,eAEA,iBADA,uCACA,CAGF,+BACE,cAIA,iBADA,gBADA,eADA,gBAIA,qBAGF,+BAIE,mDADA,6BADA,gBADA,cAGA,CAEA,uCACE,WAIJ,mCAOE,mBAFA,aAHA,YAIA,uBAFA,oBADA,kBAFA,UAMA,CAEA,uCACE,WAIJ,qCAME,6DADA,gBAJA,SAGA,gBAIA,eAEA,UA5F4B,CAqF5B,UAIA,iBALA,UAOA,kDAEA,SA3F2B,CA6F3B,kDAQE,gCAFA,WAFA,eAFA,UAjG0B,CAoG1B,eApG0B,CAgG1B,kBAMA,kBAJA,SAKA,CAIJ,2CAEE,cAIA,WAFA,gBA9GiC,CA2GjC,kBAEA,QAEA,SAhH4B,CAmH5B,uDAME,gCAFA,WADA,eAtH0B,CAoH1B,kBAIA,kBAHA,KAIA,CAGF,iDACE,OAEA,6DACE,SA7HwB,CAiI5B,iDACE,QAEA,6DACE,UArIwB,CA0I9B,0CACE,kBAEA,OAAM,CADN,KACA,CAEA,uDAEE,WADA,QAhJ0B,CAsJhC,6BAEE,sBAiBA,gBAlBA,6BAkBA,CAfA,2GAEE,YAEA,8OAGE,gBADA,YACA,CAGF,uHACE,UCtKN,uBAQE,oBADA,aADA,YAFA,OAHA,eAEA,MAMA,uBACA,8BALA,WAHA,wBAQA,CAGF,4BACE,uBAGF,8BAEE,2BADA,qBACA,CAGF,oBASE,gCALA,aAFA,OAGA,eAJA,MAMA,gBACA,qCALA,YAGA,UAGA,CAGF,2BACE,6BAGF,2BACE,cAGF,aAiBE,gDAAkD,CAClD,oDAAsD,CACtD,wDAA0D,CAC1D,yCAA0C,CAR1C,wBnCrDa,CmCsDb,wCAHA,sCACA,8BAGA,anCnDe,CmCoDf,iCANA,aAJA,oBAGA,eAPA,kBAKA,sBAJA,gBAEA,8BADA,kDAIA,SAa2C,CAE3C,oBACE,iBAIJ,0BAEE,mBADA,aAEA,cAEA,8BACE,UACA,YACA,mBAGF,+BACE,gBACA,uBACA,mBAIJ,kCACE,WAGF,oBACE,2BAGF,qBAGE,oBAFA,uBAGA,aAFA,sBAIA,QAAO,CADP,SACA,CAGF,gBAKE,uBnCpGiB,CmCoGjB,iBnCpGiB,CmCqGjB,gCALA,gBACA,SACA,SAGA,CAGF,2BACE,SAGF,gBACE,UAEA,yCAEE,sBACA,cACA,WACA,gBACA,eAEA,qDAME,4DAA8D,CAC9D,gEAAkE,CAClE,oEAAsE,CACtE,qDAAsD,CARtD,wBnC1Hc,CmC2Hd,oDACA,anC/HW,CmCgIX,4CAKuD,CCxI3D,iCAaE,mBAJA,wBpCRW,CoCSX,oCAPA,mBAEA,aASA,6DAHA,aATA,WAUA,uBARA,eAEA,YAUA,0BACA,kDAhBA,UAcA,UAEA,CAGF,yBACE,2BAGF,sBAEE,apCvBa,CoCwBb,0BAFA,eAEA,CAIJ,yBACE,qCACE,cCjCJ,aACE,aAEA,0BAEE,8BADA,YACA,CAGF,6BACE,oBACA,gEAIA,kGAEE,arCNY,CqCOZ,2BAIA,wCACE,kBADF,yEACE,kBAKF,4FACE,mBADF,sDACE,mBC5BR,gBACE,aAEA,6BAEE,8BADA,YACA,CAGF,gCACE,oBACA,gEAIA,6CACE,uBAGF,2GAEE,YtCRc,CsCSd,4BAIA,2CACE,kBAGF,4CACE,mBALF,4EACE,kBAGF,6EACE,mBAKF,kGACE,mBAGF,oGACE,kBALF,yDACE,mBAGF,0DACE,kBCvCN,qCAEE,aADA,YACA,CAEA,2CACE,OAIJ,sCAIE,oCAHA,WAEA,YADA,UAEA,CAGF,8BASE,yBAJA,aACA,eAHA,gBADA,WASA,+JACE,CADF,uJACE,CAOF,mBACA,kDAJA,8EAZA,kBAGA,aACA,kBAOA,6GALA,gEATA,UAmBA,CAEA,4CAIE,qBAHA,eACA,eACA,eACA,CAEA,kDACE,sBAKN,8BAEE,aADA,YACA,CAEA,oDACE,avCrDW,CuCsDX,0BAIJ,qCAEE,WAGE,mDACE,kBADF,oFACE,kBAKF,kHACE,mBADF,iEACE,mBCzER,eACE,aAEA,4BAEE,8BADA,YACA,CAGF,+BACE,oBACA,gEAIA,4CACE,uBAGF,wGAEE,axCTa,CwCUb,4BAIA,0CACE,kBAGF,2CACE,mBALF,2EACE,kBAGF,4EACE,mBAKF,gGACE,mBAGF,kGACE,kBALF,wDACE,mBAGF,yDACE,kBCvCN,+BAGE,aADA,aADA,eAEA,CAEA,qDACE,azCJW,CyCKX,0BAIJ,sCAEE,WAGE,oDACE,kBADF,qFACE,kBAKF,oHACE,mBADF,kEACE,mBCzBR,SACE,aAKA,eACA,YALA,SACA,SAIA,CAEA,uBACE,mBAEA,mCACE,iBAGF,qCACE,kB1COsB,C0CNtB,0CACA,YACA,WCnBN,wBAIE,iB3CIiB,C2CHjB,gCAGA,iB3CawB,C2CZxB,uCAHA,mBACA,iBANA,eAEA,cADA,cAOA,CAGA,uCACE,YAGF,mDACE,YACA,kBAEA,qDACE,cCtBN,mBAGE,iBAAiB,CAFjB,YAEkB,CAElB,kCAEE,aACA,mBAFA,aAEA,CAEA,mDACE,aACA,sBACA,iBACA,cAEA,uDAEE,WADA,SACA,CAIJ,yDACE,gBCvBN,gBACE,aAEA,eADA,gBACA,CAEA,gCAKE,mBAEA,sBAHA,aAEA,uBAJA,kBACA,gBAFA,cAMA,CAEA,gDAEE,mBADA,YACA,CAGF,sCACE,aAGF,8CACE,eAEA,oDACE,4F7CCiB,gC6CIrB,iDACE,uCACA,iBACA,8BAIJ,uCAKE,mBADA,aAEA,uBAJA,kBACA,gBAFA,cAKA,CAEA,6CACE,0BCjDN,QAGE,qBAFA,YACA,mBAEA,sBAEA,cACE,qCAAsC,CACtC,uCAAwC,CACxC,sCAAuC,CAGzC,iBAME,yDAA2D,CAC3D,qDAAuD,CACvD,yDAA2D,CAC3D,uDAAyD,CACzD,iEAAmE,CACnE,8CAA+C,CAV/C,wB9CLgB,C8CMhB,6CACA,a9CVa,C8CWb,qCAOgD,CAGlD,oBAEE,yB9CxBc,C8CyBd,uCACA,aAHA,kCAGA,CAEA,kCAEE,mBADA,aACA,CAIJ,0BACE,aACA,mCAEA,4BACE,YAGF,kCACE,cAIJ,aAGE,mBADA,aAEA,yBAHA,+DAGA,CAGF,8BACE,oBAEA,2CAEE,YADA,mBACA,CAIJ,mBACE,wCAGF,oBACE,OACA,YAGF,kBACE,yCAGF,yBASE,+BAAgC,CAChC,iBAAiB,CALjB,cADA,gBAEA,kBAHA,cADA,gBAKA,uBANA,kBASkB,CAGpB,wBACE,YAEA,kBADA,UACA,CAGF,wBACE,mBAGF,0BACE,aACA,8BACA,gBAEA,4BACE,qBACA,qBAIJ,sBAME,WAJA,kBADA,gBAGA,gBACA,uBAFA,kBAGA,CAGF,sBACE,aACA,YAGF,uBACE,aACA,cAEA,wCAEE,YADA,WACA,CAEA,kDACE,a9ChIc,C8CiId,+BAIJ,uCACE,kBAIJ,qBACE,oBACA,mBAGF,iBACE,kBAGF,uDAGE,uBAKA,oBAJA,gBAEA,iBADA,gBAEA,eALA,iBAMA,CAGF,yEAKE,aAAY,CADZ,kBADA,WAEA,CAGF,2BACE,kBAIA,iDAME,qCAFA,SAHA,WACA,cAKA,oBAJA,kBAEA,UAEA,CAGF,4CAEE,qBAIA,yDAME,qCALA,WACA,cAKA,oBAJA,kBACA,QACA,UAEA,CAKN,oCAGE,kBADA,kBACA,CAGF,8CAEE,mBACA,gBACA,uBACA,mBAGF,uBACE,eAGF,iBAIE,aACA,eAFA,gBADA,gBADA,gBAIA,CAEA,mBACE,kBAIJ,oBACE,YAGF,qBACE,wCAEA,kCACE,a9CzOa,C8C0Ob,4BAIJ,yBACE,0CAGA,YAFA,iBACA,UACA,CAGF,uBAEE,cAAa,CADb,sBACA,CAEA,8BAEE,YAEA,yCADA,sBAFA,UAGA,CAIJ,uBACE,uBACA,sBAGF,kBACE,GACE,UAGF,GACE,WAIJ,wBAGE,aACA,sCAHA,kBACA,UAEA,CAEA,0BAEE,MAAK,CADL,aACA,CAIJ,eAME,aACA,iBALA,aACA,kBAEA,gBAJA,mBAGA,sBAGA,CAEA,uFAGE,iBAEA,mBADA,iBACA,CAGF,2DAGE,gBADA,sBACA,CAGF,gCAEE,cAEA,kBAHA,gBAEA,iBACA,CAGF,4BACE,cAGF,2BACE,aACA,iBAEA,kCACE,YAIJ,uBAGE,cAFA,cACA,gBACA,CAIJ,oBAEE,gBAAe,CADf,aACA,CAGF,oBACE,OAGF,6BACE,sCAGF,eAEE,aACA,gBAFA,UAEA,CAGF,oBAKE,mBADA,aAHA,OACA,gBACA,iBAEA,CAEA,2BAME,kDALA,WAEA,YAEA,OAHA,kBAEA,SAEA,CAIJ,oBACE,wCACA,gEAEA,gCACE,uCACA,gBAEA,kBADA,wBACA,CAGF,iCAEE,gBADA,mBAEA,gBAGF,sCACE,0BAIJ,yBACE,yBACE,iBAGF,qBAEE,YADA,UACA,CAIA,8BAEE,YADA,UACA,EC7ZN,8CACE,kBAGF,yBACE,qCACA,8CACA,iB/CUoB,C+CTpB,qCACA,a/CTa,C+CUb,0BACA,cAEA,cADA,YACA,CAEA,yCACE,oBAGF,kDACE,aAEA,8BACA,mBAFA,UAEA,CAGF,+CACE,gBAIJ,cAEE,mBADA,UACA,CCrCJ,cAIE,qBAGA,iBAAiB,CALjB,uBhDOiB,CgDPjB,iBhDOiB,CgDNjB,gCAEA,qBAEkB,CAElB,oBACE,qCAAsC,CACtC,uCAAwC,CACxC,sCAAuC,CAGzC,qBAME,aACA,iBALA,aACA,kBAEA,gBAJA,mBAGA,sBAGA,CAEA,yGAGE,iBAEA,mBADA,iBACA,CAGF,uEAGE,gBADA,sBACA,CAGF,sCAEE,cAEA,kBAHA,gBAEA,iBACA,CAGF,kCACE,cAGF,iCACE,aACA,iBAEA,wCACE,YAIJ,6BAGE,cAFA,cACA,gBACA,CAIJ,yBACE,cAGF,uCACE,ahD1De,CgD2Df,4BAQF,sFACE,ahDrEc,CgDsEd,2BAGF,qCAEE,YhDzEgB,CgD0EhB,4BAGF,qCACE,ahDhFc,CgDiFd,2BC5FF,6BAEE,oBAGF,+BACE,ajDFa,CiDGb,0BAGF,6BACE,kBAEA,mDAKE,SADA,OAEA,oBALA,kBAEA,QADA,KAIA,CAIA,0DACE,2FAOR,cACE,sBAGE,4CACE,aAGF,yCACE,mBAIJ,uCACE,mBAGF,2BACE,aACA,OACA,iBAEA,WAAU,CADV,YACA,CAEA,6CAEE,YADA,UACA,CAGF,kCACE,uBAAwB,CACxB,mBAAoB,CAKtB,2CACE,ajDhEW,CiDiEX,0BAKF,2CACE,SjDjEW,CiDkEX,sBAIJ,oDAIE,aACA,8BAFA,yBADA,cAGA,CAEA,8EACE,cACA,eACA,gBACA,uBACA,mBAKJ,sBACE,OAGF,mBACE,mBAGF,kCACE,OAEA,WAAU,CADV,iBACA,CAEA,2CACE,cACA,iBAGF,gDACE,kBAIA,+DACE,kBAKN,oCACE,gBAGF,oCAEE,qBAMA,aADA,WAEA,iBACA,8BAPA,oCAFA,YAIA,gBADA,kBAEA,UAIA,CAEA,qDACE,OACA,gBACA,uBAGF,8CACE,mBACA,eACA,uBACA,mBAGF,6CACE,kBAGF,oDACE,SACA,iBAGF,uCAIE,cACA,gBAHA,gBACA,UAFA,oBAIA,CAEA,6CACE,oBAIJ,sCAGE,gBCnLN,WACE,yBAEA,uBAME,sBALA,aAGA,+BADA,wBADA,iCAGA,UACA,CAEA,yBACE,gCAIJ,6BAGE,mBADA,aADA,UAEA,CAGF,8BAKE,eAJA,qBAEA,cACA,kBAFA,iBAGA,CAGF,sBAEE,qBADA,cACA,CAGF,iBAEE,aAGF,sBASE,oBlDvCa,CkDwCb,8CATA,mBACA,WAGA,qBAEA,gBACA,gBAJA,kBAEA,oBAHA,SAOA,CAGF,wCAaE,iCANA,sCACA,8BANA,aAIA,OAHA,kBACA,eACA,MAMA,wBADA,yBADA,8BARA,WAWA,wBACA,CAEA,gDAEE,gBADA,0BACA,CAIJ,wCAEE,mBAQA,wBlDlFW,CkDmFX,uCACA,kCACA,+BAJA,wBARA,aAKA,YAHA,8BAIA,iBACA,kBAHA,WADA,oCASA,CAEA,gDACE,OAGF,+CACE,gBACA,iBAIJ,iBACE,OAEA,8BACE,YAIJ,iCAQE,wBlDlHW,CkDmHX,mCAHA,alD7Ga,CkD8Gb,0BAJA,0CAFA,gBAGA,kBACA,kBAHA,WAOA,CAEA,gDAEE,gBACA,gBAFA,SAEA,CAEA,uDACE,gBAEA,gBADA,QACA,CAGF,6DACE,gBAGF,sEACE,gBACA,gBAMJ,8CACE,aAGF,2DACE,aClJN,WAEE,qBADA,oBAGA,yBADA,uBACA,CAEA,qBACE,WAGF,uDAEE,YAGF,6BACE,cAGF,0BACE,YAGF,wBACE,anDpBa,CmDqBb,mCC1BJ,YACE,WACA,yBAEA,kBACE,8CAGF,cACE,gCAGF,uBAKE,sBAJA,aAGA,4CADA,mCADA,wCAKA,YACA,gBAFA,eAEA,CAGF,uCACE,kBAAmB,CACnB,kBAAmB,CACnB,eAAgB,CAEhB,8HACE,CAOJ,iCAEE,4CADA,kCACA,CAGF,6CACE,4KACE,CASF,4DAEE,apDjDW,CoDkDX,mCAGF,mCACE,wBpDxDS,CoDyDT,iDACA,apDxDW,CoDyDX,0CAGF,qCACE,apD7DW,CoD8DX,2CAGF,oCAGE,wBpDtES,CoDuET,iDAHA,apDlEW,CoDmEX,yCAEA,CAIJ,kBACE,eACA,kBACA,mBAEA,wBADA,mCACA,CAEA,yBAPF,kBASI,qBAGF,wBAIE,wBpD3FS,CoD4FT,2CAGA,SACA,OAPA,kDADA,oDAEA,4CAGA,kBAIA,OAAM,CAHN,KAGA,CAGF,sBACE,qBACA,4BAIJ,sBAGE,YAFA,iBAGA,kBAFA,SAEA,CAEA,sCACE,apD9GW,CoD+GX,gCAIJ,sBACE,mBAGF,qBACE,kBAGF,kBAKE,aAJA,OAKA,eAHA,4BADA,iCAEA,eAEA,CAEA,wBACE,yBACA,iBAIJ,oBACE,UC9IF,4BAGE,oEAGF,oBAEE,aADA,iBACA,CCTJ,sBAIE,gBAFA,gBACA,gBAFA,UAGA,CAEA,kCAIE,mCtDDe,CsDCf,yBtDDe,CsDEf,gCAJA,aACA,8BAIA,gBAGF,2BAGE,sBADA,oCADA,uBAEA,CAEA,+BACE,kBAEA,0CACE,gBAIJ,6BACE,aAGF,iDACE,iBAIA,gBAFA,gBADA,YAEA,8BAEA,WAGF,gCACE,eACA,cAGF,kCAEE,kBADA,cACA,CAIJ,4BACE,aACA,sBACA,gBAGF,4BACE,aACA,8BAGA,oCACE,OAGF,sCACE,aAIJ,yBACE,kCACE,mBAGF,2BAIE,sBtDxEa,CsDwEb,iBtDxEa,CsDyEb,gCAHA,gBAIA,cALA,SAKA,CAEA,+BACE,kBAIJ,4BAEE,cACA,mBAFA,SAEA,EC/FN,iCACE,uBAGF,uBACE,cAEA,kBADA,eAGA,gBADA,UACA,CAEA,8BAPF,uBAQI,eAGF,yCACE,gBAEA,qDACE,sBCnBN,iCACE,uBAGF,uBACE,cAEA,kBADA,eAGA,gBADA,UACA,CAEA,8BAPF,uBAQI,eCZJ,sCACE,uBAGF,4BACE,cAEA,kBADA,eAGA,gBADA,UACA,CAEA,8BAPF,4BAQI,eCVJ,oBAQE,mBAFA,aACA,sBAHA,oBAHA,eACA,sCACA,WAEA,iCAGA,CAEA,mCAKE,aAEA,cACA,mBAJA,2BAEA,mBALA,oBACA,kBACA,UAKA,CAEA,mDACE,cAIJ,kCACE,2CACA,CAEA,oFAFA,wCAGE,CAIJ,oCACE,gDACA,CAEA,wFAFA,0CAGE,CAIJ,oCACE,iDACA,CAEA,wFAFA,0CAGE,CAIJ,iCACE,iDACA,CAEA,kFAFA,0CAGE,CAIJ,kCACE,mBAEA,wDACE,WCpEN,OCIE,wB5DAa,oC4DFb,YACA,sBACA,CAHF,iBAKE,qBAEA,kB5DasB,sC4DVpB,cAMA,QACA,CAGA,qCACA,8BACA,CATF,UACE,CAGA,MACA,CAIA,oBARA,iBACA,CAGA,OACA,CAJA,KACA,CAGA,SAIA,aAIJ,mCACE,0BAEA,oBACE,cACA,WACA,kBACA,eAGF,eACE,CACA,SADA,WAEA,8BAIJ,oCAEE,4BACA,+BACA,8GACA,CAOA,0CACA,CACA,qBACA,CARA,qBACA,aACA,CAIA,SACA,CAHA,sBACA,CAHA,qBACA,sCACA,CAKA,oCACA,gDACA,CAHA,2CACA,CAXA,iBAEA,CAWA,SACA,gEAEA,6BACE,yJAEA,YAEE,+FAKF,kB5DvDoB,sC4D0DlB,8CAIJ,eACE,yBACA,qFAOA,QACA,CALF,UAEE,CAIA,MACA,qBALA,iBACA,CAEA,OACA,CAHA,KAKA,4CAGF,eACE,4CAKA,kBADA,sBACA,CAFF,kBAGE,qMAYE,mBALA,qBACA,CAJF,0CAEE,CAEA,QACA,CAHA,YACA,CAEA,aACA,CACA,gBACA,CAFA,aAGA,gBAUJ,iBACA,CAEA,wB5DhIa,oC4D4Hb,oBACA,CACA,sBAIA,qCARF,2BACE,kEAeE,CARF,qBAEA,wB5DnIa,sC4DqIX,CAGA,oCAHA,UAIA,wCAGF,a5DzIe,+B4D4Ib,gRAKA,sBAGE,uBAIJ,4BACE,0B5D3Jc,4C4D6Jd,4BAGF,yB5DhKgB,2C4DkKd,uDAIA,aACE,6HAEA,a5DxKW,kC4D2KT,8DAGF,wB5DhLS,gD4DkLP,c5DhLS,yC4DkLT,gEAGF,a5DrLW,0C4DuLT,+DAGF,a5D1LW,yC4D4LT,kCAKN,kBACE,CAEA,oCACA,sDACA,kDAJA,iBACA,oCAIA,yCAEA,qBACE,CACA,WACA,CAFA,qDACA,CAEA,kBADA,UAEA,6CAEA,eACE,gCAKN,kBACE,CAEA,iDAFA,iBACA,oCAEA,oCAEA,eACE,eAOJ,kBACA,CAEA,gCALF,2BACE,kEACA,CAEA,kBACA,CAFA,oBAGA,OD1OF,sBACE,uBACA,sBAEA,0BACA,iBACA,0BACA,iBACA,mBACA,MAGF,cACE,MASA,kCACA,kCACA,CAJA,a3DlBe,0B2DoBf,CALF,sBACE,4CACA,SACA,CAKA,eACA,mBAFA,0BAGA,aAEA,YACE,0BAOJ,EACE,sCACE,qBAEA,sBACE,sDAGF,2BAEE,CACA,+BADA,8BAEA,4BAMF,kBACE,CAEA,sCAFA,oBAGA,uCAEA,uFACE,iDAEA,qIAEI,0FAEF,iDAGF,qIAEI,0FAEF,qCAIJ,uFACE,+CAEA,qIAEI,uFAEF,+CAGF,qIAEI,uFAEF,MAQN,4BADF,oDAEE,IAKF,a3DxGe,2B2DuGjB,oBAGE,IAGF,QACE,aAGF,oBACE,CACA,iBADA,iBAEA,6CAGF,U3DtHiB,uB2D0Hf,sLAKA,iBAGE,KAKF,wB3D3Ia,uC2D6Ib,CAEA,iCACA,+BACA,sBACA,CALA,yB3D5IgB,uC2D8IhB,CAGA,2BACA,gBATF,wBAUE,UAGF,iBACE,QAGF,iBACE,yBACA,qBAIA,gBADF,wBAEE,gBAGF,iBACE,kBACA,gBAGF,gBACE,iBAWA,iCACA,8CACA,yBAHA,2BACA,CAFA,qBACA,CANA,WACA,CAEA,MACA,CALF,cACE,CAIA,WACA,CAJA,wBACA,cAQA,WAMA,gCACA,iDACA,CALF,oBACE,aACA,oBACA,CAEA,aACA,aAGF,kBACE,mBACA,gBACA,uBACA,oGACA,kGACA,oGACA,CAUA,wBACA,eACA,CAPE,qCAEF,CAJA,2FAEE,CAEF,sBACA,CAIA,sBACA,CAJA,aACA,CAGA,gBACA,iBAdA,iBAeA,iCAPA,qBACA,CAPA,YAyBE,CAZF,oBAEA,kCACE,CAQA,oBAJA,YACA,CAHA,0BACA,CAEA,uCACA,uCACA,+BAEA,uCAEA,+BACE,2BAGF,SACE,kCAGF,eACE,CACA,iBADA,aAEA,iCAGF,6CACE,CAMA,8CACA,CAJA,6CACA,CACA,iBACA,CAFA,eACA,CAEA,wEAPA,eAEA,yBAMA,sEAIA,sDAEI,+CACA,0EAFF,oBAGE,0EAEA,aACE,QACA,yDAKN,6BACE,0CAMJ,oBACE,+DAMA,iBACE,MACA,2BASJ,oBAFA,qBACA,CAHF,YACE,2BACA,CACA,WAEA,2CAKE,sCAFJ,2FAIE,mBAKE,6CAFJ,6HAKE,4BAII,6CAFJ,6HAKE,qBAKF,6BACA,CAFF,2BACE,CACA,SACA,6BAGE,kCADF,aAEE,mLAGF,wBAKE,0BACA,CAKA,mGAKF,YACE,cAKN,iBACE,iBAMA,wB3D5Wa,oC2D8Wb,YACA,kB3D7VoB,mC2D+VpB,CACA,4F3DxVuB,+B2D0VvB,CAVA,a3DxWe,6B2D0Wf,CAKA,cACA,CAGA,sBACA,6CAFA,aACA,CAZF,wBACE,CADF,qBACE,CADF,gBAcE,0BAEA,sBACE,iEAGF,a3D3Xe,6B2D8Xb,mCAGF,WACE,uBAGF,qCACE,oCACA,wBAUA,wB3DnZW,4C2D4Yb,0GAEI,sCAOF,4EAJA,a3D/Ya,oC2DwZX,0BAOF,wB3DjaW,6C2D8Zb,kBAKE,kFAJA,a3D7Za,qC2DsaX,yBAMF,wB3D9aW,2C2DgbX,2GAEE,sCAGF,+EATF,a3D1ae,oC2DwbX,wBAOF,mC3DpbmB,uD2DibrB,a3D5be,yC2Dicb,kBAIJ,eACE,YACA,CAQA,sBACA,eAFA,cACA,CAPA,cACA,CAEA,mBACA,CAFA,cACA,CAEA,iBACA,CAPA,YACA,CAIA,SACA,CAJA,kBAQA,wBAEA,a3Dlde,0B2Dodb,6BAGF,UACE,6CAIA,a3DzdkB,+B2D2dhB,uBAKN,gBAUE,CASA,wB3Dzfa,sC2D2fb,CAXA,WAEA,kB3D/dsB,qC2DietB,mGAEE,8BAGF,CAQA,qBACA,CAPA,a3DrfoB,+B2DufpB,CAKA,oBACA,CANA,sBACA,wCACA,cACA,CAKA,oBACA,CADA,YACA,CAFA,aACA,CALA,QACA,CAKA,0BAHA,iBAIA,kDA7BE,eACA,CAFF,eACE,CACA,eACA,aACA,kLA4BF,kBAGE,WACA,2DAGF,eACE,YACA,CACA,eACA,QAFA,QAGA,2DAGF,YACE,0HAIE,uCAFF,qDACE,gEAEA,yTAIA,UAGE,kGAcF,wB3DnjBS,sC2DqjBT,CANA,kBACA,8BACA,8BACA,CAOA,qBACA,kBACA,CAhBA,UACA,CAFA,oBACA,CAFF,aACE,CAcA,eACA,CAXA,YACA,CAQA,eACA,CANA,iBACA,CAQA,gBALA,iBACA,CAXA,yBACA,CAQA,kBACA,CATA,WAeA,mIAKF,a3D/jBa,+B2DikBX,oVAIA,UAGE,2GAeF,wB3DzlBS,sC2D2lBT,CAPA,iB3DnkBqB,wC2DqkBrB,8BACA,8BACA,CAOA,qBACA,kBACA,CAjBA,WACA,CAFA,oBACA,CAFF,aACE,CAeA,eACA,CAZA,YACA,CASA,eACA,CANA,iBACA,CAQA,gBALA,iBACA,CAZA,oBACA,CASA,kBACA,CAVA,WAgBA,iEAIJ,eACE,UAMF,oCADF,uBAEE,QAKA,wB3DpnBa,oC2DknBf,a3D/mBiB,0B2DmnBf,sBAGF,4BACE,CADF,yBACE,CADF,oBACE,2HAIE,aAFF,SAGE,YAIJ,aACE,WACA,YAIA,mBACA,CAFF,iBACE,CACA,qBACA,mBAGE,cADF,iBAEE,oCAGE,6BADF,yBAEE,qCAIA,4BADF,wBAEE,KAKN,UACE,eAGF,YACE,QAKA,kBACA,CAHF,qBACE,qBACA,CAQA,cACA,CAFA,iBACA,CAFA,eACA,CAJA,YACA,CAKA,aACA,CATA,cACA,gBACA,CASA,eACA,CATA,aACA,CAKA,iBACA,CAEA,uBARA,qBACA,CAKA,kBAGA,2BAEA,oB3D9qBe,8C2DgrBb,WACA,wCACA,QAMF,iB3D5qBwB,wC2D0qB1B,cACE,gBAGA,cAEA,mC3DvrBqB,sD2DyrBnB,c3DpsBa,oC2DssBb,6BAEA,a3DxsBa,yC2D0sBX,gBAIJ,oC3DlsBuB,yD2DosBrB,c3DhtBa,sC2DktBb,+BAEA,a3DptBa,2C2DstBX,gBAIJ,wDACE,sCACA,+BAEA,0CACE,CAOJ,mBAGF,yB3D1uBkB,uC2D4uBhB,mBAEA,yBACE,oBAKF,oCACA,kDACA,kB3DpuBsB,sC2DiuBxB,YAKE,qBAGF,kBACE,kBACA,8BAME,cADA,YACA,CAJF,iBACE,CACA,OACA,CAFA,KAIA,uDAKF,eAEE,iFAKF,cAGE,YAIJ,WACE,aAGF,iBACE,0BAEA,YAHF,YAII,gBAGF,oBACE,cACA,WACA,qBAIJ,cACE,0BAMA,OAFA,eACA,CAFF,iBACE,CACA,SAEA,0BAGF,eACE,YACE,kBAIJ,GACE,sBACE,IAGF,wBACE,wBAIJ,GACE,uBACE,KAGF,6BACE,KAGF,8BACE,KAGF,6BACE,KAGF,8BACE,KAGF,6BACE,KAGF,8BACE,IAGF,uBACE,wCAKJ,sBAEE,qCAGF,SAEE,gCAUA,kBACA,CAPF,aACE,CACA,UACA,YACA,gBACA,CAEA,SACA,mBAHA,kBACA,CALA,SAQA,CE93BF,qBAEE,yCADA,sCACA,CAGF,4BAKE,oBADA,aAEA,sBALA,kCAKA,CCXF,cACE,UAEA,kDAOE,oBALA,2CACA,gBAGA,aAEA,sBAPA,kCAOA,CAGF,gCAEE,yCADA,sCACA,CAGF,qDACE,uBAAwB,CACxB,mBAAoB,CAEpB,kBAGF,wCAEE,2CACA,eAAc,CAFd,uCAEA,CAGA,sFAGE,oBADA,aAEA,sBAIJ,8CACE,mCAGF,mCACE,2CACA,gBAGF,iTAKE,mBAGF,kEACE,wCAIF,mDAKE,2CAHA,4DACA,4BACA,iEACA,CAGF,sCACE,2CCvEJ,uBAME,wBAAuB,CADvB,0BADA,eADA,iBADA,gBADA,eAKA,CAEA,0BACE,gBACA,SACA,UAGF,yBACE,cAEA,aACA,kBAFA,eAEA,CAEA,+BAGE,a/DlBW,C+DmBX,qCAKgD,CAGlD,2EANE,qDAAuD,CACvD,yDAA2D,CAC3D,6DAA+D,CAC/D,8CAA+C,CAR/C,wB/Ddc,C+Ded,4CAoBgD,CAVlD,4CAIE,a/DhCW,C+DiCX,sCAJA,kBASgD,CAEhD,kDACE,0BAIJ,6BAEE,kBADA,iBACA,CAIJ,0BAEE,uB/DhDe,C+DgDf,iB/DhDe,C+DiDf,gCACA,UAEA,uCAGE,8B/D9CkB,C+D+ClB,kDAHA,+B/D5CkB,C+D6ClB,kDAEA,CAGF,qCACE,YAKN,cACE,kBACA,YAEA,sCACE,sBAGF,2BAEE,wBAAuB,CADvB,yBACA,CAGF,mCAEE,eAGA,aAJA,SAEA,gEACA,UACA,CAEA,uDACE,gBACA,uBACA,mBAGF,uCACE,iBACA,yBAGF,kDACE,eACA,YAIJ,4CACE,a/D5Ga,C+D6Gb,+BACA,yBAGF,qBACE,gCCtHF,qBACE,mBACA,WAGA,qBAEA,gBACA,gBAFA,oBAHA,SAMA,CAGF,4CAHE,qCALA,iBAoBA,CAZF,uBAIE,mCAQA,8BAXA,gBAKA,sBAJA,cAOA,iBACA,gBAFA,aALA,iBAIA,oBAKA,CAGF,2BACE,kBAGF,mBACE,gBAGF,gCACE,oEACA,UAIA,sCAEE,mBACA,eAFA,iBAEA,CAEA,mGAEE,gBACA,WCjDR,cACE,aAEA,wBAEE,cADA,gBACA,CAGF,uBACE,sBAEA,6BAME,cADA,mBAFA,gBADA,kBAEA,gBAHA,UAKA,CAEA,uEAME,oEAJA,WACA,aAGA,CAGF,0CACE,WAEA,6DAME,oEAHA,SAFA,OACA,OAIA,CAIJ,kCAGE,4BACA,6BAEA,oBAJA,cAGA,oBAJA,UAKA,CAIJ,iDACE,aAIJ,wBACE,mBAEA,yBAHF,wBAII,iBAGF,kCACE,cAGF,8BACE,cAGA,sBADA,kBADA,eAEA,CAEA,yEAOE,kEAHA,WADA,gBADA,aAKA,CAGF,oCACE,YAGF,qCACE,YAGF,2CAEE,aACA,sBAFA,cAEA,CAEA,yBALF,2CAMI,eAGF,8DAME,kEAHA,SADA,QADA,KAKA,CAGF,kDAKE,kEAHA,WADA,YAIA,CAGF,2DACE,gBAIJ,mCAME,6BADA,0BAHA,uBADA,OASA,gBADA,oBANA,eACA,cAGA,iBACA,+BAEA,CAEA,yBAZF,mCAgBI,kBADA,iCAFA,mBACA,iCAEA,CAEA,yCACE,cAOV,wBACE,cACA,aAEA,gCACE,aAGF,kDAEE,aACA,sBAFA,WAEA,CAEA,sEACE,OAIJ,wCACE,gBAIJ,mBAGE,gBAFA,kBACA,kBACA,CAEA,gCACE,UAEA,sCACE,UAIJ,0BACE,uBAEA,ajEvLW,CiEwLX,mCAFA,SAEA,CAGF,uBAGE,gBAFA,gBACA,kBACA,CAIJ,oBAGE,sBAFA,aACA,iBACA,CAEA,qDAEE,cACA,cAIJ,2BAEE,aACA,cAFA,iBAEA,CAGE,8CACE,WACA,kBACA,UAKN,4BAME,2CADA,oBADA,iBADA,gBADA,qBADA,iBAKA,CAEA,yBARF,4BASI,cCzON,YAME,iBAAiB,CALjB,YAKkB,CAElB,kCANA,gBACA,uBACA,kBAUE,CANF,sBAKE,qBADA,eAHA,cAKA,CAGF,8BACE,kBACA,cAGF,6BAIE,kBlEFwB,CkEGxB,0CAHA,aADA,kBAEA,WAEA,CAEA,6CACE,aCjCN,gBAME,sBACA,eANA,aACA,mBAEA,WACA,gBAFA,aAIA,CAEA,uBACE,aAGF,sBACE,6CACA,sCAGF,qCACE,iBAGF,uCAIE,qBAFA,sBACA,gBAFA,UAGA,CAGF,yBAEE,oBACA,8BACA,gBAHA,UAGA,CAGF,+BACE,mBAGF,uCAIE,cACA,oCAFA,gBAFA,uBACA,kBAGA,CAGF,8BAME,anE/Ca,CmEgDb,2BANA,oBAIA,eAHA,gBAEA,uBADA,mBAKA,WAGF,kBACE,+BAEA,oBADA,oBACA,CAIA,8CACE,aAGF,2CACE,mBAIJ,wBACE,kBnEjDwB,CmEkDxB,0CAGF,mCACE,kBAAmB,CAEnB,kBAGF,8BACE,oCCtFJ,iBAME,iBAAiB,CALjB,aACA,SACA,SACA,gBAEkB,CAElB,mCAGE,OAFA,iBAGA,WAAU,CAFV,eAEA,CAIA,+BAEE,YADA,yCAGA,sBADA,UACA,CAIJ,8DAEE,qBACA,eACA,gBAEA,uBADA,kBACA,CAGF,kCACE,OACA,iBACA,YCpCF,sBACE,aACA,iBAEA,4BACE,WAIJ,uBACE,kBAGF,uBACE,qBAGF,iCAEE,6CADA,cACA,CAGF,0BAIE,iBADA,YADA,cADA,kBAIA,0CCzBJ,WAEE,eAAc,CADd,eACA,CAGF,uBAKE,atENe,CsEOf,2BAHA,aADA,gBAEA,uBAHA,WAKA,CCTI,oEACE,aAGF,iEACE,mBAKN,yCAEE,UACA,kBACA,UAHA,sBAGA,CAEA,gDAEE,oBADA,gBACA,CAIJ,iCACE,eAEA,mGAEE,avEzBW,CuE0BX,0BAIJ,+BACE,WAGF,oCACE,aACA,oBAEA,uDACE,qCAAsC,CACtC,uCAAwC,CACxC,sCAAuC,CAI3C,sCACE,mBACA,WAGF,uEAEE,kBAGF,8BACE,kBvElC0B,CuEmC1B,4CACA,aACA,cAGF,kCAEE,YACA,eAEA,kBADA,oBAEA,WALA,iBAKA,CAME,8EAEE,YACA,qBAFA,kBAEA,CAMJ,qGAEE,mBAKF,iGAEE,SvEtFW,CuEuFX,mCAIJ,0CAGE,uBAFA,aACA,sBAEA,cACA,eACA,WAGF,gCAGE,kBAFA,aACA,mBAEA,yBAEA,kCACE,6CAGF,wCAEE,sDACA,4DAFA,4CAEA,CAGF,oDACE,qBAGF,mDACE,YAKF,kCACE,6CAGF,wCAEE,sDACA,2DAIA,sFANA,4CAOE,CAIJ,mDACE,WAOF,kHACE,WAIJ,+BACE,UAIJ,6BAKE,avE3Ke,CuE4Kf,iCAHA,eADA,eADA,kBAGA,+DAEA,CCnLF,WACE,aACA,YAEA,4BAIE,aAHA,YAEA,iBADA,UAEA,CAGF,2BAEE,uCAOA,4BACA,kEATA,sBAEA,aACA,sBAIA,SADA,8CADA,iBADA,UAKA,CAEA,iCACE,gBAIJ,yBAGE,aACA,sBAFA,YAGA,oBAJA,cAIA,CAGF,mBAGE,wBxEnCW,CwEoCX,mCAFA,SADA,gBAIA,UAGF,8BACE,2CAGF,2BAIE,iBADA,YADA,cADA,kBAIA,0CAGF,kCAWE,mBAJA,wBxE1DW,CwE2DX,oCALA,mBASA,6DAMA,eATA,aAPA,aAQA,uBAMA,UAZA,kBACA,YACA,WAQA,oBACA,kDAEA,kBAhBA,YAYA,UAKA,CAEA,0CACE,UACA,mBAGF,oCAEE,axE5EW,CwE6EX,0BAFA,aAEA,CAGF,wDAKE,mBAJA,eACA,SACA,iBACA,aAEA,kBAGF,sDAGE,qBADA,aAEA,YAHA,UAGA,CAEA,6DACE,WCrGN,+BAEE,aACA,mBAFA,cAGA,8BACA,kBAGF,oBAGE,gBAFA,gBACA,eACA,CAGF,2BAEE,iBADA,gBAEA,WChBF,uBAKE,8DAJA,aACA,iBAGA,CAEA,8BACE,eAGF,yBACE,eCZN,cAKE,qBAAqB,CAJrB,OACA,gBAGsB,CAEtB,6BACE,oBAGF,mCACE,cAEA,uCAIE,iBADA,eAFA,yCACA,qBAEA,CAEA,6CAEE,YADA,UACA,CAIJ,uDAGE,oCACA,iB3ETkB,C2EUlB,qCAJA,aACA,YAGA,CAEA,gFAME,0CAFA,uBAHA,aACA,gBAGA,gBAFA,gBAGA,CAGF,iFAEE,kBADA,aAEA,mBAGF,iKAOE,sBALA,gBAGA,gBACA,mBAHA,uBACA,kBAGA,CAKN,oCAGE,mBAFA,aACA,uBAEA,YAKF,sCAGE,mBAFA,aACA,uBAEA,YCzEJ,uBACE,yB5EEgB,C4EDhB,uCACA,eACA,kBAGF,yBAEI,qDACE,cAEA,cADA,uBAEA,mBAKN,eAGE,uB5EZiB,C4EYjB,iB5EZiB,C4EajB,gCAHA,qBAGA,CAGF,sBAKE,wB5E5Ba,C4E6Bb,sCAHA,gCADA,mBADA,qBAGA,YAEA,CAGF,wBAEE,aACA,uBAFA,aAEA,CAEA,sCAKE,sBAFA,eADA,qBAEA,cAHA,UAIA,CAGF,uCACE,iBAIJ,cACE,YAGF,OAEE,mBADA,YACA,CAEA,gBACE,cAGA,gBACA,uBACA,mBAGF,8BAPE,a5E1Da,C4E2Db,yBAcA,CARF,cACE,cAEA,iBAEA,gBADA,oBAEA,kBAJA,UAMA,CAIJ,sBACE,aACA,kBClFA,8CACE,iBCLJ,mBAIA,YACE,sBACA,YACA,+BAEA,YACE,mBACA,iCAEA,WACE,sCAIJ,YACE,YACA,iCAKA,YACA,CAFA,QACA,CACA,sBAHF,eAIE,6BAGF,gBACE,gBACA,gCAGF,YACE,sBACA,CACA,aACA,mBAFA,cAGA,uCAIA,sBACA,CAFF,yBACE,CACA,qCACA,oDAGF,aA/CiB,0BAiDf,gCAGF,gBACE,gBACA,qCAEA,eACE,mCAIJ,eACE,CACA,aADA,iBAEA,6CAEA,YACE,kCAIJ,gBACE,gBACA,6BAIA,mBADF,eAEE,yBAIA,WADF,eAEE,2BAGF,iBACE,0BAIJ,8BACE,6BACE,EC5FJ,qBAGE,mBAFA,aACA,sBAEA,YAEA,gCACE,aACA,SACA,sBACA,gBACA,gBAEA,kCACE,YAIJ,iCACE,aACA,sBAGA,mBAFA,kBACA,cACA,CAGF,4BAGE,uBADA,0BAEA,sCAHA,iBAGA,CAGF,4BAEE,kBADA,YACA,CAGF,8CACE,sDACA,eAGF,yCACE,mBAGF,8BACE,eClDJ,uCACE,aACA,mBAEA,8CAGE,SADA,kBADA,gBAGA,eACA,cAEA,yDACE,eCZN,aACE,WCDF,aACE,iBACA,gBAEA,8BACE,eCNJ,aACE,WAEA,mBAIE,oBADA,kBADA,gBADA,UAGA,CAEA,4CAGE,gBACA,gBACA,wBAHA,WAGA,CAGF,kDAEE,WChBN,WACE,aAGF,WACE,YAGF,6BAIE,apFPe,CoFQf,0BAHA,SACA,WAEA,CAEA,yCAME,qDAAuD,CACvD,yDAA2D,CAC3D,6DAA8D,CAP9D,wBpFTgB,CoFUhB,6CACA,apFba,CoFcb,qCAI+D,CCxBjE,wBACE,eCCF,6BACE,aACA,iBAEA,mCACE,WAIJ,8BACE,kBCXJ,eAGE,mBAGA,avFFe,CuFGf,0BANA,aAIA,cAHA,YAEA,sBAGA,CAEA,iCAGE,avFRa,CuFSb,0BAHA,cACA,qBAEA,CCbJ,UACE,0BAA2B,CAI3B,aACA,sBAHA,0CACA,eAEA,CAEA,6BACE,2CAGF,sBACE,aACA,OACA,sBACA,gBAGF,kCACE,cAGF,uBACE,kBAGF,sBAEE,gBADA,oBACA,CAGF,+CAGE,sBACA,YAAW,CAFX,eAEA,CAGF,0BAIE,iBADA,YADA,cADA,kBAIA,0CAGF,eACE,cAGF,wBACE,sCAEA,uCACE,cCzDN,qBAEE,oBADA,aAEA,sBAEA,4CACE,gBAGF,oCAIE,uBAFA,YACA,cAFA,eAGA,CCXJ,cACE,2CACA,gBACA,mCAEA,2CAEE,yCAOA,mDACE,aACA,sBAIJ,+BACE,aACA,mBACA,6BAEA,oCACE,OACA,WACA,eC3BJ,+BACE,mCAEA,6EAEE,yCAGF,4CACE","sources":["webpack://pleroma_fe/./src/components/modal/modal.vue","webpack://pleroma_fe/./node_modules/vue-virtual-scroller/dist/vue-virtual-scroller.css","webpack://pleroma_fe/./src/components/login_form/login_form.vue","webpack://pleroma_fe/./src/components/media_upload/media_upload.vue","webpack://pleroma_fe/./src/components/scope_selector/scope_selector.vue","webpack://pleroma_fe/./src/_variables.scss","webpack://pleroma_fe/./src/components/checkbox/checkbox.vue","webpack://pleroma_fe/./src/components/popover/popover.vue","webpack://pleroma_fe/./src/components/still-image/still-image.vue","webpack://pleroma_fe/./src/components/emoji_picker/emoji_picker.scss","webpack://pleroma_fe/./src/components/emoji_input/emoji_input.vue","webpack://pleroma_fe/./src/components/select/select.vue","webpack://pleroma_fe/./src/components/poll/poll_form.vue","webpack://pleroma_fe/./src/components/flash/flash.vue","webpack://pleroma_fe/./src/components/attachment/attachment.scss","webpack://pleroma_fe/./src/components/gallery/gallery.vue","webpack://pleroma_fe/./src/components/user_avatar/user_avatar.vue","webpack://pleroma_fe/./src/components/mention_link/mention_link.scss","webpack://pleroma_fe/./src/components/mentions_line/mentions_line.scss","webpack://pleroma_fe/./src/components/hashtag_link/hashtag_link.scss","webpack://pleroma_fe/./src/components/rich_content/rich_content.scss","webpack://pleroma_fe/./src/components/poll/poll.vue","webpack://pleroma_fe/./src/components/status_body/status_body.scss","webpack://pleroma_fe/./src/components/link-preview/link-preview.vue","webpack://pleroma_fe/./src/components/status_content/status_content.vue","webpack://pleroma_fe/./src/components/post_status_form/post_status_form.vue","webpack://pleroma_fe/./src/components/remote_follow/remote_follow.vue","webpack://pleroma_fe/./src/components/dialog_modal/dialog_modal.vue","webpack://pleroma_fe/./src/components/moderation_tools/moderation_tools.vue","webpack://pleroma_fe/./src/components/account_actions/account_actions.vue","webpack://pleroma_fe/./src/components/user_note/user_note.vue","webpack://pleroma_fe/./src/components/user_card/user_card.scss","webpack://pleroma_fe/./src/components/user_panel/user_panel.vue","webpack://pleroma_fe/./src/components/navigation/navigation_entry.vue","webpack://pleroma_fe/./src/components/navigation/navigation_pins.vue","webpack://pleroma_fe/./src/components/nav_panel/nav_panel.vue","webpack://pleroma_fe/./src/components/features_panel/features_panel.vue","webpack://pleroma_fe/./src/components/who_to_follow_panel/who_to_follow_panel.vue","webpack://pleroma_fe/./src/components/shout_panel/shout_panel.vue","webpack://pleroma_fe/./src/components/media_modal/media_modal.vue","webpack://pleroma_fe/./src/components/side_drawer/side_drawer.vue","webpack://pleroma_fe/./src/components/mobile_post_status_button/mobile_post_status_button.vue","webpack://pleroma_fe/./src/components/reply_button/reply_button.vue","webpack://pleroma_fe/./src/components/favorite_button/favorite_button.vue","webpack://pleroma_fe/./src/components/react_button/react_button.vue","webpack://pleroma_fe/./src/components/retweet_button/retweet_button.vue","webpack://pleroma_fe/./src/components/extra_buttons/extra_buttons.vue","webpack://pleroma_fe/./src/components/avatar_list/avatar_list.vue","webpack://pleroma_fe/./src/components/status_popover/status_popover.vue","webpack://pleroma_fe/./src/components/user_list_popover/user_list_popover.vue","webpack://pleroma_fe/./src/components/emoji_reactions/emoji_reactions.vue","webpack://pleroma_fe/./src/components/status/status.scss","webpack://pleroma_fe/./src/components/report/report.scss","webpack://pleroma_fe/./src/components/notification/notification.scss","webpack://pleroma_fe/./src/components/notifications/notifications.scss","webpack://pleroma_fe/./src/components/mobile_nav/mobile_nav.vue","webpack://pleroma_fe/./src/components/search_bar/search_bar.vue","webpack://pleroma_fe/./src/components/desktop_nav/desktop_nav.scss","webpack://pleroma_fe/./src/components/list/list.vue","webpack://pleroma_fe/./src/components/user_reporting_modal/user_reporting_modal.vue","webpack://pleroma_fe/./src/components/edit_status_modal/edit_status_modal.vue","webpack://pleroma_fe/./src/components/post_status_modal/post_status_modal.vue","webpack://pleroma_fe/./src/components/status_history_modal/status_history_modal.vue","webpack://pleroma_fe/./src/components/global_notice_list/global_notice_list.vue","webpack://pleroma_fe/./src/App.scss","webpack://pleroma_fe/./src/panel.scss","webpack://pleroma_fe/./src/components/thread_tree/thread_tree.vue","webpack://pleroma_fe/./src/components/conversation/conversation.vue","webpack://pleroma_fe/./src/components/timeline_menu/timeline_menu.vue","webpack://pleroma_fe/./src/components/timeline/timeline.scss","webpack://pleroma_fe/./src/components/tab_switcher/tab_switcher.scss","webpack://pleroma_fe/./src/components/chat_title/chat_title.vue","webpack://pleroma_fe/./src/components/chat_list_item/chat_list_item.scss","webpack://pleroma_fe/./src/components/basic_user_card/basic_user_card.vue","webpack://pleroma_fe/./src/components/chat_new/chat_new.scss","webpack://pleroma_fe/./src/components/chat_list/chat_list.vue","webpack://pleroma_fe/./src/components/chat_message/chat_message.scss","webpack://pleroma_fe/./src/components/chat/chat.scss","webpack://pleroma_fe/./src/components/follow_card/follow_card.vue","webpack://pleroma_fe/./src/hocs/with_load_more/with_load_more.scss","webpack://pleroma_fe/./src/components/user_profile/user_profile.vue","webpack://pleroma_fe/./src/components/search/search.vue","webpack://pleroma_fe/./src/components/interface_language_switcher/interface_language_switcher.vue","webpack://pleroma_fe/./src/components/registration/registration.vue","webpack://pleroma_fe/./src/components/password_reset/password_reset.vue","webpack://pleroma_fe/./src/components/follow_request_card/follow_request_card.vue","webpack://pleroma_fe/./src/components/terms_of_service_panel/terms_of_service_panel.vue","webpack://pleroma_fe/./src/components/staff_panel/staff_panel.vue","webpack://pleroma_fe/./src/components/mrf_transparency_panel/mrf_transparency_panel.scss","webpack://pleroma_fe/./src/components/lists_card/lists_card.vue","webpack://pleroma_fe/./src/components/lists/lists.vue","webpack://pleroma_fe/./src/components/lists_user_search/lists_user_search.vue","webpack://pleroma_fe/./src/components/panel_loading/panel_loading.vue","webpack://pleroma_fe/./src/components/lists_edit/lists_edit.vue","webpack://pleroma_fe/./src/components/announcement_editor/announcement_editor.vue","webpack://pleroma_fe/./src/components/announcement/announcement.vue","webpack://pleroma_fe/./src/components/announcements_page/announcements_page.vue"],"sourcesContent":["\n.modal-view {\n z-index: var(--ZI_modals);\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow: auto;\n pointer-events: none;\n animation-duration: 0.2s;\n animation-name: modal-background-fadein;\n opacity: 0;\n\n > * {\n pointer-events: initial;\n }\n\n &.modal-background {\n pointer-events: initial;\n background-color: rgb(0 0 0 / 50%);\n }\n\n &.open {\n opacity: 1;\n }\n}\n\n@keyframes modal-background-fadein {\n from {\n background-color: rgb(0 0 0 / 0%);\n }\n\n to {\n background-color: rgb(0 0 0 / 50%);\n }\n}\n",".vue-recycle-scroller{position:relative}.vue-recycle-scroller.direction-vertical:not(.page-mode){overflow-y:auto}.vue-recycle-scroller.direction-horizontal:not(.page-mode){overflow-x:auto}.vue-recycle-scroller.direction-horizontal{display:flex}.vue-recycle-scroller__slot{flex:auto 0 0}.vue-recycle-scroller__item-wrapper{flex:1;box-sizing:border-box;overflow:hidden;position:relative}.vue-recycle-scroller.ready .vue-recycle-scroller__item-view{position:absolute;top:0;left:0;will-change:transform}.vue-recycle-scroller.direction-vertical .vue-recycle-scroller__item-wrapper{width:100%}.vue-recycle-scroller.direction-horizontal .vue-recycle-scroller__item-wrapper{height:100%}.vue-recycle-scroller.ready.direction-vertical .vue-recycle-scroller__item-view{width:100%}.vue-recycle-scroller.ready.direction-horizontal .vue-recycle-scroller__item-view{height:100%}.resize-observer[data-v-b329ee4c]{position:absolute;top:0;left:0;z-index:-1;width:100%;height:100%;border:none;background-color:transparent;pointer-events:none;display:block;overflow:hidden;opacity:0}.resize-observer[data-v-b329ee4c] object{display:block;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1}","\n@import \"../../variables\";\n\n.login-form {\n display: flex;\n flex-direction: column;\n padding: 0.6em;\n\n .btn {\n min-height: 2em;\n width: 10em;\n }\n\n .register {\n flex: 1 1;\n }\n\n .login-bottom {\n margin-top: 1em;\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n }\n\n .form-group {\n display: flex;\n flex-direction: column;\n padding: 0.3em 0.5em 0.6em;\n line-height: 24px;\n }\n\n .form-bottom {\n display: flex;\n padding: 0.5em;\n height: 32px;\n\n button {\n width: 10em;\n }\n\n p {\n margin: 0.35em;\n padding: 0.35em;\n display: flex;\n }\n }\n\n .error {\n text-align: center;\n animation-name: shakeError;\n animation-duration: 0.4s;\n animation-timing-function: ease-in-out;\n }\n}\n","\n@import \"../../variables\";\n\n.media-upload {\n cursor: pointer; // We use