diff options
Diffstat (limited to 'lib')
47 files changed, 774 insertions, 328 deletions
diff --git a/lib/healthcheck.ex b/lib/healthcheck.ex index 646fb3b9d..32aafc210 100644 --- a/lib/healthcheck.ex +++ b/lib/healthcheck.ex @@ -29,13 +29,13 @@ defmodule Pleroma.Healthcheck do end defp assign_db_info(healthcheck) do - database = Application.get_env(:pleroma, Repo)[:database] + database = Pleroma.Config.get([Repo, :database]) query = "select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;" result = Repo.query!(query) - pool_size = Application.get_env(:pleroma, Repo)[:pool_size] + pool_size = Pleroma.Config.get([Repo, :pool_size]) db_info = Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states -> diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index 238c1acf2..bc97b39ca 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -49,7 +49,7 @@ defmodule Pleroma.Conversation do with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity), "Create" <- activity.data["type"], object <- Pleroma.Object.normalize(activity), - "Note" <- object.data["type"], + true <- object.data["type"] in ["Note", "Question"], ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do {:ok, conversation} = create_for_ap_id(ap_id) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 6390cce4c..7d12eff7f 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Emoji do @ets __MODULE__.Ets @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}] - @groups Application.get_env(:pleroma, :emoji)[:groups] + @groups Pleroma.Config.get([:emoji, :groups]) @doc false def start_link do @@ -112,7 +112,7 @@ defmodule Pleroma.Emoji do # Compat thing for old custom emoji handling & default emoji, # it should run even if there are no emoji packs - shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || [] + shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], []) emojis = (load_from_file("config/emoji.txt") ++ diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 3e3b9fe97..607843a5b 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Formatter do alias Pleroma.User alias Pleroma.Web.MediaProxy - @safe_mention_regex ~r/^(\s*(?<mentions>@.+?\s+)+)(?<rest>.*)/s + @safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index d1da746de..e5e78ee4f 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -104,7 +104,6 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do paragraphs, breaks and links are allowed through the filter. """ - @markup Application.get_env(:pleroma, :markup) @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) require HtmlSanitizeEx.Scrubber.Meta @@ -142,9 +141,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do Meta.allow_tag_with_these_attributes("span", []) # allow inline images for custom emoji - @allow_inline_images Keyword.get(@markup, :allow_inline_images) - - if @allow_inline_images do + if Pleroma.Config.get([:markup, :allow_inline_images]) do # restrict img tags to http/https only, because of MediaProxy. Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"]) @@ -168,7 +165,6 @@ defmodule Pleroma.HTML.Scrubber.Default do # credo:disable-for-previous-line # No idea how to fix this one… - @markup Application.get_env(:pleroma, :markup) @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) Meta.remove_cdata_sections_before_scrub() @@ -213,7 +209,7 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"]) Meta.allow_tag_with_these_attributes("span", []) - @allow_inline_images Keyword.get(@markup, :allow_inline_images) + @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images]) if @allow_inline_images do # restrict img tags to http/https only, because of MediaProxy. @@ -228,9 +224,7 @@ defmodule Pleroma.HTML.Scrubber.Default do ]) end - @allow_tables Keyword.get(@markup, :allow_tables) - - if @allow_tables do + 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", []) @@ -239,9 +233,7 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes("tr", []) end - @allow_headings Keyword.get(@markup, :allow_headings) - - if @allow_headings do + 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", []) @@ -249,9 +241,7 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes("h5", []) end - @allow_fonts Keyword.get(@markup, :allow_fonts) - - if @allow_fonts do + if Pleroma.Config.get([:markup, :allow_fonts]) do Meta.allow_tag_with_these_attributes("font", ["face"]) end diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex index 558005c19..c216cdcb1 100644 --- a/lib/pleroma/http/connection.ex +++ b/lib/pleroma/http/connection.ex @@ -32,9 +32,11 @@ defmodule Pleroma.HTTP.Connection do defp hackney_options(opts) do options = Keyword.get(opts, :adapter, []) adapter_options = Pleroma.Config.get([:http, :adapter], []) + proxy_url = Pleroma.Config.get([:http, :proxy_url], nil) @hackney_options |> Keyword.merge(adapter_options) |> Keyword.merge(options) + |> Keyword.merge(proxy: proxy_url) end end diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index c5f720bc9..c96ee7353 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -65,12 +65,9 @@ defmodule Pleroma.HTTP do end def process_request_options(options) do - config = Application.get_env(:pleroma, :http, []) - proxy = Keyword.get(config, :proxy_url, nil) - - case proxy do + case Pleroma.Config.get([:http, :proxy_url]) do nil -> options - _ -> options ++ [proxy: proxy] + proxy -> options ++ [proxy: proxy] end end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 844264307..46f2107b1 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -127,10 +127,15 @@ defmodule Pleroma.Notification do def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity) when type in ["Create", "Like", "Announce", "Follow"] do - users = get_notified_from_activity(activity) - - notifications = Enum.map(users, fn user -> create_notification(activity, user) end) - {:ok, notifications} + object = Object.normalize(activity) + + unless object && object.data["type"] == "Answer" do + users = get_notified_from_activity(activity) + notifications = Enum.map(users, fn user -> create_notification(activity, user) end) + {:ok, notifications} + else + {:ok, []} + end end def create_notifications(_), do: {:ok, []} @@ -166,7 +171,16 @@ defmodule Pleroma.Notification do def get_notified_from_activity(_, _local_only), do: [] def skip?(activity, user) do - [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed] + [ + :self, + :blocked, + :muted, + :followers, + :follows, + :non_followers, + :non_follows, + :recently_followed + ] |> Enum.any?(&skip?(&1, activity, user)) end @@ -179,12 +193,6 @@ defmodule Pleroma.Notification do User.blocks?(user, %{ap_id: actor}) end - def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}), - do: true - - def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}), - do: true - def skip?(:muted, activity, user) do actor = activity.data["actor"] @@ -201,12 +209,32 @@ defmodule Pleroma.Notification do User.following?(follower, user) end + def skip?( + :non_followers, + activity, + %{info: %{notification_settings: %{"non_followers" => false}}} = user + ) do + actor = activity.data["actor"] + follower = User.get_cached_by_ap_id(actor) + !User.following?(follower, user) + end + def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do actor = activity.data["actor"] followed = User.get_cached_by_ap_id(actor) User.following?(user, followed) end + def skip?( + :non_follows, + activity, + %{info: %{notification_settings: %{"non_follows" => false}}} = user + ) do + actor = activity.data["actor"] + followed = User.get_cached_by_ap_id(actor) + !User.following?(user, followed) + end + def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do actor = activity.data["actor"] diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index cc6fc9c5d..4b181ec59 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -35,6 +35,9 @@ defmodule Pleroma.Object do |> unique_constraint(:ap_id, name: :objects_unique_apid_index) end + def get_by_id(nil), do: nil + def get_by_id(id), do: Repo.get(Object, id) + def get_by_ap_id(nil), do: nil def get_by_ap_id(ap_id) do @@ -195,4 +198,34 @@ defmodule Pleroma.Object do _ -> {:error, "Not found"} end end + + def increase_vote_count(ap_id, name) do + with %Object{} = object <- Object.normalize(ap_id), + "Question" <- object.data["type"] do + multiple = Map.has_key?(object.data, "anyOf") + + options = + (object.data["anyOf"] || object.data["oneOf"] || []) + |> Enum.map(fn + %{"name" => ^name} = option -> + Kernel.update_in(option["replies"]["totalItems"], &(&1 + 1)) + + option -> + option + end) + + data = + if multiple do + Map.put(object.data, "anyOf", options) + else + Map.put(object.data, "oneOf", options) + end + + object + |> Object.change(%{data: data}) + |> update_and_set_cache() + else + _ -> :noop + end + end end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index bb9388d4f..ca980c629 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -1,4 +1,5 @@ defmodule Pleroma.Object.Fetcher do + alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Web.ActivityPub.Transmogrifier @@ -6,8 +7,6 @@ defmodule Pleroma.Object.Fetcher do require Logger - @httpoison Application.get_env(:pleroma, :httpoison) - defp reinject_object(data) do Logger.debug("Reinjecting object #{data["id"]}") @@ -78,7 +77,7 @@ defmodule Pleroma.Object.Fetcher do with true <- String.starts_with?(id, "http"), {:ok, %{body: body, status: code}} when code in 200..299 <- - @httpoison.get( + HTTP.get( id, [{:Accept, "application/activity+json"}] ), diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index effc154bf..4dc4e9279 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.FederatingPlug do end def call(conn, _opts) do - if Keyword.get(Application.get_env(:pleroma, :instance), :federating) do + if Pleroma.Config.get([:instance, :federating]) do conn else conn diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index a3f177fec..285d57309 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.ReverseProxy do + alias Pleroma.HTTP + @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++ ~w(if-unmodified-since if-none-match if-range range) @resp_cache_headers ~w(etag date last-modified cache-control) @@ -59,9 +61,6 @@ defmodule Pleroma.ReverseProxy do * `http`: options for [hackney](https://github.com/benoitc/hackney). """ - @hackney Application.get_env(:pleroma, :hackney, :hackney) - @httpoison Application.get_env(:pleroma, :httpoison, HTTPoison) - @default_hackney_options [] @inline_content_types [ @@ -97,7 +96,7 @@ defmodule Pleroma.ReverseProxy do hackney_opts = @default_hackney_options |> Keyword.merge(Keyword.get(opts, :http, [])) - |> @httpoison.process_request_options() + |> HTTP.process_request_options() req_headers = build_req_headers(conn.req_headers, opts) @@ -147,7 +146,7 @@ defmodule Pleroma.ReverseProxy do Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}") method = method |> String.downcase() |> String.to_existing_atom() - case @hackney.request(method, url, headers, "", hackney_opts) do + case :hackney.request(method, url, headers, "", hackney_opts) do {:ok, code, headers, client} when code in @valid_resp_codes -> {:ok, code, downcase_headers(headers), client} @@ -197,7 +196,7 @@ defmodule Pleroma.ReverseProxy do duration, Keyword.get(opts, :max_read_duration, @max_read_duration) ), - {:ok, data} <- @hackney.stream_body(client), + {:ok, data} <- :hackney.stream_body(client), {:ok, duration} <- increase_read_duration(duration), sent_so_far = sent_so_far + byte_size(data), :ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)), diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 190ed9f3a..237544337 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -4,11 +4,10 @@ defmodule Pleroma.Uploaders.MDII do alias Pleroma.Config + alias Pleroma.HTTP @behaviour Pleroma.Uploaders.Uploader - @httpoison Application.get_env(:pleroma, :httpoison) - # MDII-hosted images are never passed through the MediaPlug; only local media. # Delegate to Pleroma.Uploaders.Local def get_file(file) do @@ -25,7 +24,7 @@ defmodule Pleroma.Uploaders.MDII do query = "#{cgi}?#{extension}" with {:ok, %{status: 200, body: body}} <- - @httpoison.post(query, file_data, [], adapter: [pool: :default]) do + HTTP.post(query, file_data, [], adapter: [pool: :default]) do remote_file_name = String.split(body) |> List.first() public_url = "#{files}/#{remote_file_name}.#{extension}" {:ok, {:url, public_url}} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 653dec95f..474cd8c1a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -366,9 +366,7 @@ defmodule Pleroma.User do end def follow(%User{} = follower, %User{info: info} = followed) do - user_config = Application.get_env(:pleroma, :user) - deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) - + deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) ap_followers = followed.follower_address cond do @@ -760,7 +758,7 @@ defmodule Pleroma.User do from(s in subquery(boost_search_rank_query(distinct_query, for_user)), order_by: [desc: s.search_rank], - limit: 20 + limit: 40 ) end diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 6397e2737..fb9ab92ab 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -42,12 +42,17 @@ defmodule Pleroma.User.Info do field(:hide_follows, :boolean, default: false) field(:hide_favorites, :boolean, default: true) field(:pinned_activities, {:array, :string}, default: []) - field(:flavour, :string, default: nil) field(:mascot, :map, default: nil) field(:emoji, {:array, :map}, default: []) + field(:pleroma_settings_store, :map, default: %{}) field(:notification_settings, :map, - default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true} + default: %{ + "followers" => true, + "follows" => true, + "non_follows" => true, + "non_followers" => true + } ) # Found in the wild @@ -68,10 +73,15 @@ defmodule Pleroma.User.Info do end def update_notification_settings(info, settings) do + settings = + settings + |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end) + |> Map.new() + notification_settings = info.notification_settings |> Map.merge(settings) - |> Map.take(["remote", "local", "followers", "follows"]) + |> Map.take(["followers", "follows", "non_follows", "non_followers"]) params = %{notification_settings: notification_settings} @@ -209,7 +219,8 @@ defmodule Pleroma.User.Info do :hide_followers, :hide_favorites, :background, - :show_role + :show_role, + :pleroma_settings_store ]) end @@ -241,14 +252,6 @@ defmodule Pleroma.User.Info do |> validate_required([:settings]) end - def mastodon_flavour_update(info, flavour) do - params = %{flavour: flavour} - - info - |> cast(params, [:flavour]) - |> validate_required([:flavour]) - end - def mascot_update(info, url) do params = %{mascot: url} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 48aaabe94..47115aa6e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -107,6 +107,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def decrease_replies_count_if_reply(_object), do: :noop + def increase_poll_votes_if_vote(%{ + "object" => %{"inReplyTo" => reply_ap_id, "name" => name}, + "type" => "Create" + }) do + Object.increase_vote_count(reply_ap_id, name) + end + + def increase_poll_votes_if_vote(_create_data), do: :noop + def insert(map, local \\ true, fake \\ false) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map, fake), @@ -182,40 +191,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do public = "https://www.w3.org/ns/activitystreams#Public" if activity.data["type"] in ["Create", "Announce", "Delete"] do - Pleroma.Web.Streamer.stream("user", activity) - Pleroma.Web.Streamer.stream("list", activity) + object = Object.normalize(activity) + # Do not stream out poll replies + unless object.data["type"] == "Answer" do + Pleroma.Web.Streamer.stream("user", activity) + Pleroma.Web.Streamer.stream("list", activity) - if Enum.member?(activity.data["to"], public) do - Pleroma.Web.Streamer.stream("public", activity) + if Enum.member?(activity.data["to"], public) do + Pleroma.Web.Streamer.stream("public", activity) - if activity.local do - Pleroma.Web.Streamer.stream("public:local", activity) - end - - if activity.data["type"] in ["Create"] do - object = Object.normalize(activity) + if activity.local do + Pleroma.Web.Streamer.stream("public:local", activity) + end - object.data - |> Map.get("tag", []) - |> Enum.filter(fn tag -> is_bitstring(tag) end) - |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) + if activity.data["type"] in ["Create"] do + object.data + |> Map.get("tag", []) + |> Enum.filter(fn tag -> is_bitstring(tag) end) + |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) - if object.data["attachment"] != [] do - Pleroma.Web.Streamer.stream("public:media", activity) + if object.data["attachment"] != [] do + Pleroma.Web.Streamer.stream("public:media", activity) - if activity.local do - Pleroma.Web.Streamer.stream("public:local:media", activity) + if activity.local do + Pleroma.Web.Streamer.stream("public:local:media", activity) + end end end + else + # TODO: Write test, replace with visibility test + if !Enum.member?(activity.data["cc"] || [], public) && + !Enum.member?( + activity.data["to"], + User.get_cached_by_ap_id(activity.data["actor"]).follower_address + ), + do: Pleroma.Web.Streamer.stream("direct", activity) end - else - # TODO: Write test, replace with visibility test - if !Enum.member?(activity.data["cc"] || [], public) && - !Enum.member?( - activity.data["to"], - User.get_cached_by_ap_id(activity.data["actor"]).follower_address - ), - do: Pleroma.Web.Streamer.stream("direct", activity) end end end @@ -234,6 +245,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, activity} <- insert(create_data, local, fake), {:fake, false, activity} <- {:fake, fake, activity}, _ <- increase_replies_count_if_reply(create_data), + _ <- increase_poll_votes_if_vote(create_data), # Changing note count prior to enqueuing federation task in order to avoid # race conditions on updating user.info {:ok, _actor} <- increase_note_count_if_public(actor, activity), @@ -398,16 +410,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def block(blocker, blocked, activity_id \\ nil, local \\ true) do - ap_config = Application.get_env(:pleroma, :activitypub) - unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked) - outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks) + outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks]) + unfollow_blocked = Pleroma.Config.get([:activitypub, :unfollow_blocked]) - with true <- unfollow_blocked do + if unfollow_blocked do follow_activity = fetch_latest_follow(blocker, blocked) - - if follow_activity do - unfollow(blocker, blocked, nil, local) - end + if follow_activity, do: unfollow(blocker, blocked, nil, local) end with true <- outgoing_blocks, @@ -479,6 +487,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public from(activity in Activity) + |> maybe_preload_objects(opts) |> restrict_blocked(opts) |> restrict_recipients(recipients, opts["user"]) |> where( @@ -491,6 +500,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ^context ) ) + |> exclude_poll_votes(opts) |> order_by([activity], desc: activity.id) end @@ -498,7 +508,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def fetch_activities_for_context(context, opts \\ %{}) do context |> fetch_activities_for_context_query(opts) - |> Activity.with_preloaded_object() |> Repo.all() end @@ -506,7 +515,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Pleroma.FlakeId.t() | nil def fetch_latest_activity_id_for_context(context, opts \\ %{}) do context - |> fetch_activities_for_context_query(opts) + |> fetch_activities_for_context_query(Map.merge(%{"skip_preload" => true}, opts)) |> limit(1) |> select([a], a.id) |> Repo.one() @@ -652,20 +661,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_tag(query, _), do: query - defp restrict_to_cc(query, recipients_to, recipients_cc) do - from( - activity in query, - where: - fragment( - "(?->'to' \\?| ?) or (?->'cc' \\?| ?)", - activity.data, - ^recipients_to, - activity.data, - ^recipients_cc - ) - ) - end - defp restrict_recipients(query, [], _user), do: query defp restrict_recipients(query, recipients, nil) do @@ -819,6 +814,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted_reblogs(query, _), do: query + defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query + + defp exclude_poll_votes(query, _) do + if has_named_binding?(query, :object) do + from([activity, object: o] in query, + where: fragment("not(?->>'type' = ?)", o.data, "Answer") + ) + else + query + end + end + defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query defp maybe_preload_objects(query, _) do @@ -878,6 +885,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_pinned(opts) |> restrict_muted_reblogs(opts) |> Activity.restrict_deactivated_users() + |> exclude_poll_votes(opts) end def fetch_activities(recipients, opts \\ %{}) do @@ -906,9 +914,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp maybe_update_cc(activities, _, _), do: activities - def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do + def fetch_activities_bounded_query(query, recipients, recipients_with_public) do + from(activity in query, + where: + fragment("? && ?", activity.recipients, ^recipients) or + (fragment("? && ?", activity.recipients, ^recipients_with_public) and + "https://www.w3.org/ns/activitystreams#Public" in activity.recipients) + ) + end + + def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do fetch_activities_query([], opts) - |> restrict_to_cc(recipients_to, recipients_cc) + |> fetch_activities_bounded_query(recipients, recipients_with_public) |> Pagination.fetch_paginated(opts) |> Enum.reverse() end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index ad2ca1e54..0182bda46 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do plug(:relay_active? when action in [:relay]) def relay_active?(conn, _) do - if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do + if Pleroma.Config.get([:instance, :allow_relay]) do conn else conn diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index 1aaa20050..10ceef715 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.ActivityPub.MRF do @callback filter(Map.t()) :: {:ok | :reject, Map.t()} - def filter(object) do - get_policies() + def filter(policies, %{} = object) do + policies |> Enum.reduce({:ok, object}, fn policy, {:ok, object} -> policy.filter(object) @@ -16,10 +16,10 @@ defmodule Pleroma.Web.ActivityPub.MRF do end) end + def filter(%{} = object), do: get_policies() |> filter(object) + def get_policies do - Application.get_env(:pleroma, :instance, []) - |> Keyword.get(:rewrite_policy, []) - |> get_policies() + Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies() end defp get_policies(policy) when is_atom(policy), do: [policy] diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 890d70a7a..433d23c5f 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -74,8 +74,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do actor_host ), user <- User.get_cached_by_ap_id(object["actor"]), - true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"], - true <- user.follower_address in object["cc"] do + true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do to = List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ [user.follower_address] diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex new file mode 100644 index 000000000..765704389 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex @@ -0,0 +1,40 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do + alias Pleroma.Config + alias Pleroma.Web.ActivityPub.MRF + + require Logger + + @behaviour MRF + + defp lookup_subchain(actor) do + with matches <- Config.get([:mrf_subchain, :match_actor]), + {match, subchain} <- Enum.find(matches, fn {k, _v} -> String.match?(actor, k) end) do + {:ok, match, subchain} + else + _e -> {:error, :notfound} + end + end + + @impl true + def filter(%{"actor" => actor} = message) do + with {:ok, match, subchain} <- lookup_subchain(actor) do + Logger.debug( + "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{ + inspect(subchain) + }" + ) + + subchain + |> MRF.filter(message) + else + _e -> {:ok, message} + end + end + + @impl true + def filter(message), do: {:ok, message} +end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index fdebdf85c..f376e5618 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.Activity alias Pleroma.Config + alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay @@ -16,8 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do require Logger - @httpoison Application.get_env(:pleroma, :httpoison) - @moduledoc """ ActivityPub outgoing federation module. """ @@ -63,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do with {:ok, %{status: code}} when code in 200..299 <- result = - @httpoison.post( + HTTP.post( inbox, json, [ diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 49d1610a7..d22d24479 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -35,6 +35,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_likes |> fix_addressing |> fix_summary + |> fix_type end def fix_summary(%{"summary" => nil} = object) do @@ -65,7 +66,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end - def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, explicit_mentions) do + def fix_explicit_addressing( + %{"to" => to, "cc" => cc} = object, + explicit_mentions, + follower_collection + ) do explicit_to = to |> Enum.filter(fn x -> x in explicit_mentions end) @@ -76,6 +81,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do final_cc = (cc ++ explicit_cc) + |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end) |> Enum.uniq() object @@ -83,7 +89,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> Map.put("cc", final_cc) end - def fix_explicit_addressing(object, _explicit_mentions), do: object + def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object # if directMessage flag is set to true, leave the addressing alone def fix_explicit_addressing(%{"directMessage" => true} = object), do: object @@ -93,10 +99,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do object |> Utils.determine_explicit_mentions() - explicit_mentions = explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public"] + follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address - object - |> fix_explicit_addressing(explicit_mentions) + explicit_mentions = + explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection] + + fix_explicit_addressing(object, explicit_mentions, follower_collection) end # if as:Public is addressed, then make sure the followers collection is also addressed @@ -133,7 +141,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_addressing_list("cc") |> fix_addressing_list("bto") |> fix_addressing_list("bcc") - |> fix_explicit_addressing + |> fix_explicit_addressing() |> fix_implicit_addressing(followers_collection) end @@ -328,6 +336,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_content_map(object), do: object + def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do + reply = Object.normalize(reply_id) + + if reply.data["type"] == "Question" and object["name"] do + Map.put(object, "type", "Answer") + else + object + end + end + + def fix_type(object), do: object + defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do with true <- id =~ "follows", %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id), @@ -398,7 +418,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do # - tags # - emoji def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) - when objtype in ["Article", "Note", "Video", "Page"] do + when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do actor = Containment.get_actor(data) data = @@ -731,6 +751,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> set_reply_to_uri |> strip_internal_fields |> strip_internal_tags + |> set_type end # @doc @@ -898,6 +919,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do Map.put(object, "sensitive", "nsfw" in tags) end + def set_type(%{"type" => "Answer"} = object) do + Map.put(object, "type", "Note") + end + + def set_type(object), do: object + def add_attributed_to(object) do attributed_to = object["attributedTo"] || object["actor"] diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index ca8a0844b..b8159e9e5 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do require Logger - @supported_object_types ["Article", "Note", "Video", "Page"] + @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"] @supported_report_states ~w(open closed resolved) @valid_visibilities ~w(public unlisted private direct) @@ -789,4 +789,21 @@ defmodule Pleroma.Web.ActivityPub.Utils do [to, cc, recipients] end end + + def get_existing_votes(actor, %{data: %{"id" => id}}) do + query = + from( + [activity, object: object] in Activity.with_preloaded_object(Activity), + where: fragment("(?)->>'actor' = ?", activity.data, ^actor), + where: + fragment( + "(?)->>'inReplyTo' = ?", + object.data, + ^to_string(id) + ), + where: fragment("(?)->>'type' = 'Answer'", object.data) + ) + + Repo.all(query) + end end diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 93b50ee47..8965e3253 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -66,6 +66,9 @@ defmodule Pleroma.Web.ActivityPub.Visibility do Enum.any?(to, &String.contains?(&1, "/followers")) -> "private" + object.data["directMessage"] == true -> + "direct" + length(cc) > 0 -> "private" diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index e8199200e..85fb32669 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -119,6 +119,53 @@ defmodule Pleroma.Web.CommonAPI do end end + def vote(user, object, choices) do + with "Question" <- object.data["type"], + {:author, false} <- {:author, object.data["actor"] == user.ap_id}, + {:existing_votes, []} <- {:existing_votes, Utils.get_existing_votes(user.ap_id, object)}, + {options, max_count} <- get_options_and_max_count(object), + option_count <- Enum.count(options), + {:choice_check, {choices, true}} <- + {:choice_check, normalize_and_validate_choice_indices(choices, option_count)}, + {:count_check, true} <- {:count_check, Enum.count(choices) <= max_count} do + answer_activities = + Enum.map(choices, fn index -> + answer_data = make_answer_data(user, object, Enum.at(options, index)["name"]) + + ActivityPub.create(%{ + to: answer_data["to"], + actor: user, + context: object.data["context"], + object: answer_data, + additional: %{"cc" => answer_data["cc"]} + }) + end) + + object = Object.get_cached_by_ap_id(object.data["id"]) + {:ok, answer_activities, object} + else + {:author, _} -> {:error, "Poll's author can't vote"} + {:existing_votes, _} -> {:error, "Already voted"} + {:choice_check, {_, false}} -> {:error, "Invalid indices"} + {:count_check, false} -> {:error, "Too many choices"} + end + end + + defp get_options_and_max_count(object) do + if Map.has_key?(object.data, "anyOf") do + {object.data["anyOf"], Enum.count(object.data["anyOf"])} + else + {object.data["oneOf"], 1} + end + end + + defp normalize_and_validate_choice_indices(choices, count) do + Enum.map_reduce(choices, true, fn index, valid -> + index = if is_binary(index), do: String.to_integer(index), else: index + {index, if(valid, do: index < count, else: valid)} + end) + end + def get_visibility(%{"visibility" => visibility}, in_reply_to) when visibility in ~w{public unlisted private direct}, do: {visibility, get_replied_to_visibility(in_reply_to)} @@ -159,6 +206,7 @@ defmodule Pleroma.Web.CommonAPI do data, visibility ), + {poll, poll_emoji} <- make_poll_data(data), {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility), bcc <- bcc_for_list(user, visibility), context <- make_context(in_reply_to), @@ -177,13 +225,14 @@ defmodule Pleroma.Web.CommonAPI do tags, cw, cc, - sensitive + sensitive, + poll ), object <- Map.put( object, "emoji", - Formatter.get_emoji_map(full_payload) + Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji) ) do ActivityPub.create( %{ diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index d97a80dd5..9c92c6cea 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -111,6 +111,72 @@ defmodule Pleroma.Web.CommonAPI.Utils do def bcc_for_list(_, _), do: [] + def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_in}} = data) + when is_list(options) do + %{max_expiration: max_expiration, min_expiration: min_expiration} = + limits = Pleroma.Config.get([:instance, :poll_limits]) + + # XXX: There is probably a cleaner way of doing this + try do + # In some cases mastofe sends out strings instead of integers + expires_in = if is_binary(expires_in), do: String.to_integer(expires_in), else: expires_in + + if Enum.count(options) > limits.max_options do + raise ArgumentError, message: "Poll can't contain more than #{limits.max_options} options" + end + + {poll, emoji} = + Enum.map_reduce(options, %{}, fn option, emoji -> + if String.length(option) > limits.max_option_chars do + raise ArgumentError, + message: + "Poll options cannot be longer than #{limits.max_option_chars} characters each" + end + + {%{ + "name" => option, + "type" => "Note", + "replies" => %{"type" => "Collection", "totalItems" => 0} + }, Map.merge(emoji, Formatter.get_emoji_map(option))} + end) + + case expires_in do + expires_in when expires_in > max_expiration -> + raise ArgumentError, message: "Expiration date is too far in the future" + + expires_in when expires_in < min_expiration -> + raise ArgumentError, message: "Expiration date is too soon" + + _ -> + :noop + end + + end_time = + NaiveDateTime.utc_now() + |> NaiveDateTime.add(expires_in) + |> NaiveDateTime.to_iso8601() + + poll = + if Pleroma.Web.ControllerHelper.truthy_param?(data["poll"]["multiple"]) do + %{"type" => "Question", "anyOf" => poll, "closed" => end_time} + else + %{"type" => "Question", "oneOf" => poll, "closed" => end_time} + end + + {poll, emoji} + rescue + e in ArgumentError -> e.message + end + end + + def make_poll_data(%{"poll" => poll}) when is_map(poll) do + "Invalid poll" + end + + def make_poll_data(_data) do + {%{}, %{}} + end + def make_content_html( status, attachments, @@ -233,7 +299,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do tags, cw \\ nil, cc \\ [], - sensitive \\ false + sensitive \\ false, + merge \\ %{} ) do object = %{ "type" => "Note", @@ -248,12 +315,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq() } - with false <- is_nil(in_reply_to), - %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do - Map.put(object, "inReplyTo", in_reply_to_object.data["id"]) - else - _ -> object - end + object = + with false <- is_nil(in_reply_to), + %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do + Map.put(object, "inReplyTo", in_reply_to_object.data["id"]) + else + _ -> object + end + + Map.merge(object, merge) end def format_naive_asctime(date) do @@ -430,4 +500,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do {:error, "No such conversation"} end end + + def make_answer_data(%User{ap_id: ap_id}, object, name) do + %{ + "type" => "Answer", + "actor" => ap_id, + "cc" => [object.data["actor"]], + "to" => [], + "name" => name, + "inReplyTo" => object.data["id"] + } + end end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 9ef30e885..bd76e4295 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -16,17 +16,32 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Plugs.UploadedMedia) + @static_cache_control "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 - plug(Pleroma.Plugs.InstanceStatic, at: "/") + # Cache-control headers are duplicated in case we turn off etags in the future + plug(Pleroma.Plugs.InstanceStatic, + at: "/", + gzip: true, + cache_control_for_etags: @static_cache_control, + headers: %{ + "cache-control" => @static_cache_control + } + ) plug( Plug.Static, at: "/", from: :pleroma, only: - ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) + ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength + gzip: true, + cache_control_for_etags: @static_cache_control, + headers: %{ + "cache-control" => @static_cache_control + } ) plug(Plug.Static.IndexHtml, at: "/pleroma/admin/") @@ -51,7 +66,7 @@ defmodule Pleroma.Web.Endpoint do parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Jason, - length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit), + length: Pleroma.Config.get([:instance, :upload_limit]), body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} ) diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 6b0b75284..f4c9fe284 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -11,13 +11,11 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.RetryQueue + alias Pleroma.Web.OStatus alias Pleroma.Web.Websub require Logger - @websub Application.get_env(:pleroma, :websub) - @ostatus Application.get_env(:pleroma, :ostatus) - def init do # 1 minute Process.sleep(1000 * 60) @@ -87,12 +85,12 @@ defmodule Pleroma.Web.Federator do "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" end) - @websub.verify(websub) + Websub.verify(websub) end def perform(:incoming_doc, doc) do Logger.info("Got document, trying to parse") - @ostatus.handle_incoming(doc) + OStatus.handle_incoming(doc) end def perform(:incoming_ap_doc, params) do diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 1ec0f30a1..fe2fdcea1 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Conversation.Participation alias Pleroma.Filter alias Pleroma.Formatter + alias Pleroma.HTTP alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Object.Fetcher @@ -55,7 +56,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do when action in [:account_register] ) - @httpoison Application.get_env(:pleroma, :httpoison) @local_mastodon_name "Mastodon-Local" action_fallback(:errors) @@ -124,6 +124,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end) end) |> add_if_present(params, "default_scope", :default_scope) + |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value -> + {:ok, Map.merge(user.info.pleroma_settings_store, value)} + end) |> add_if_present(params, "header", :banner, fn value -> with %Plug.Upload{} <- value, {:ok, object} <- ActivityPub.upload(value, type: :banner) do @@ -143,7 +146,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do CommonAPI.update(user) end - json(conn, AccountView.render("account.json", %{user: user, for: user})) + json( + conn, + AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true}) + ) else _e -> conn @@ -153,7 +159,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def verify_credentials(%{assigns: %{user: user}} = conn, _) do - account = AccountView.render("account.json", %{user: user, for: user}) + account = + AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true}) + json(conn, account) end @@ -197,7 +205,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do languages: ["en"], registrations: Pleroma.Config.get([:instance, :registrations_open]), # Extra (not present in Mastodon): - max_toot_chars: Keyword.get(instance, :limit) + max_toot_chars: Keyword.get(instance, :limit), + poll_limits: Keyword.get(instance, :poll_limits) } json(conn, response) @@ -409,6 +418,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Object{} = object <- Object.get_by_id(id), + %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), + true <- Visibility.visible_for_user?(activity, user) do + conn + |> put_view(StatusView) + |> try_render("poll.json", %{object: object, for: user}) + else + nil -> + conn + |> put_status(404) + |> json(%{error: "Record not found"}) + + false -> + conn + |> put_status(404) + |> json(%{error: "Record not found"}) + end + end + + def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do + with %Object{} = object <- Object.get_by_id(id), + true <- object.data["type"] == "Question", + %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), + true <- Visibility.visible_for_user?(activity, user), + {:ok, _activities, object} <- CommonAPI.vote(user, object, choices) do + conn + |> put_view(StatusView) + |> try_render("poll.json", %{object: object, for: user}) + else + nil -> + conn + |> put_status(404) + |> json(%{error: "Record not found"}) + + false -> + conn + |> put_status(404) + |> json(%{error: "Record not found"}) + + {:error, message} -> + conn + |> put_status(422) + |> json(%{error: message}) + end + end + def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do conn @@ -472,12 +528,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do params |> Map.put("in_reply_to_status_id", params["in_reply_to_id"]) - idempotency_key = - case get_req_header(conn, "idempotency-key") do - [key] -> key - _ -> Ecto.UUID.generate() - end - scheduled_at = params["scheduled_at"] if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do @@ -490,17 +540,40 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do else params = Map.drop(params, ["scheduled_at"]) - {:ok, activity} = - Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> - CommonAPI.post(user, params) - end) - - conn - |> put_view(StatusView) - |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + case get_cached_status_or_post(conn, params) do + {:ignore, message} -> + conn + |> put_status(422) + |> json(%{error: message}) + + {:error, message} -> + conn + |> put_status(422) + |> json(%{error: message}) + + {_, activity} -> + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + end end end + defp get_cached_status_or_post(%{assigns: %{user: user}} = conn, params) do + idempotency_key = + case get_req_header(conn, "idempotency-key") do + [key] -> key + _ -> Ecto.UUID.generate() + end + + Cachex.fetch(:idempotency_cache, idempotency_key, fn _ -> + case CommonAPI.post(user, params) do + {:ok, activity} -> activity + {:error, message} -> {:ignore, message} + end + end) + end + def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do json(conn, %{}) @@ -1084,7 +1157,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do from([a, o] in Activity.with_preloaded_object(Activity), where: fragment("?->>'type' = 'Create'", a.data), where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, - limit: 20 + limit: 40 ) q = @@ -1346,8 +1419,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user})) - flavour = get_user_flavour(user) - initial_state = %{ meta: %{ @@ -1366,6 +1437,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do max_toot_chars: limit, mascot: User.get_mascot(user)["url"] }, + poll_limits: Config.get([:instance, :poll_limits]), rights: %{ delete_others_notice: present?(user.info.is_moderator), admin: present?(user.info.is_admin) @@ -1433,7 +1505,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do conn |> put_layout(false) |> put_view(MastodonView) - |> render("index.html", %{initial_state: initial_state, flavour: flavour}) + |> render("index.html", %{initial_state: initial_state}) else conn |> put_session(:return_to, conn.request_path) @@ -1456,43 +1528,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - @supported_flavours ["glitch", "vanilla"] - - def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params) - when flavour in @supported_flavours do - flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour) - - with changeset <- Ecto.Changeset.change(user), - changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng), - {:ok, user} <- User.update_and_set_cache(changeset), - flavour <- user.info.flavour do - json(conn, flavour) - else - e -> - conn - |> put_resp_content_type("application/json") - |> send_resp(500, Jason.encode!(%{"error" => inspect(e)})) - end - end - - def set_flavour(conn, _params) do - conn - |> put_status(400) - |> json(%{error: "Unsupported flavour"}) - end - - def get_flavour(%{assigns: %{user: user}} = conn, _params) do - json(conn, get_user_flavour(user)) - end - - defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do - flavour - end - - defp get_user_flavour(_) do - "glitch" - end - def login(%{assigns: %{user: %User{}}} = conn, _params) do redirect(conn, to: local_mastodon_root_path(conn)) end @@ -1691,7 +1726,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> String.replace("{{user}}", user) with {:ok, %{status: 200, body: body}} <- - @httpoison.get( + HTTP.get( url, [], adapter: [ diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index b82d3319b..dc32a1525 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -130,6 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do |> maybe_put_role(user, opts[:for]) |> maybe_put_settings(user, opts[:for], user_info) |> maybe_put_notification_settings(user, opts[:for]) + |> maybe_put_settings_store(user, opts[:for], opts) end defp username_from_nickname(string) when is_binary(string) do @@ -152,6 +153,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do defp maybe_put_settings(data, _, _, _), do: data + defp maybe_put_settings_store(data, %User{info: info, id: id}, %User{id: id}, %{ + with_pleroma_settings: true + }) do + data + |> Kernel.put_in([:pleroma, :settings_store], info.pleroma_settings_store) + end + + defp maybe_put_settings_store(data, _, _, _), do: data + defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do data |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin) diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 8e8f7cf31..af1dcf66d 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -22,9 +22,14 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do last_status = StatusView.render("status.json", %{activity: activity, for: user}) + # Conversations return all users except the current user. + users = + participation.conversation.users + |> Enum.reject(&(&1.id == user.id)) + accounts = AccountView.render("accounts.json", %{ - users: participation.conversation.users, + users: users, as: :user }) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e55f9b96e..6836d331a 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -240,6 +240,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do spoiler_text: summary_html, visibility: get_visibility(object), media_attachments: attachments, + poll: render("poll.json", %{object: object, for: opts[:for]}), mentions: mentions, tags: build_tags(tags), application: %{ @@ -290,8 +291,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do provider_url: page_url_data.scheme <> "://" <> page_url_data.host, url: page_url, image: image_url |> MediaProxy.url(), - title: rich_media[:title], - description: rich_media[:description], + title: rich_media[:title] || "", + description: rich_media[:description] || "", pleroma: %{ opengraph: rich_media } @@ -329,6 +330,64 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end + def render("poll.json", %{object: object} = opts) do + {multiple, options} = + case object.data do + %{"anyOf" => options} when is_list(options) -> {true, options} + %{"oneOf" => options} when is_list(options) -> {false, options} + _ -> {nil, nil} + end + + if options do + end_time = + (object.data["closed"] || object.data["endTime"]) + |> NaiveDateTime.from_iso8601!() + + expired = + end_time + |> NaiveDateTime.compare(NaiveDateTime.utc_now()) + |> case do + :lt -> true + _ -> false + end + + voted = + if opts[:for] do + existing_votes = + Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object) + + existing_votes != [] or opts[:for].ap_id == object.data["actor"] + else + false + end + + {options, votes_count} = + Enum.map_reduce(options, 0, fn %{"name" => name} = option, count -> + current_count = option["replies"]["totalItems"] || 0 + + {%{ + title: HTML.strip_tags(name), + votes_count: current_count + }, current_count + count} + end) + + %{ + # Mastodon uses separate ids for polls, but an object can't have + # more than one poll embedded so object id is fine + id: object.id, + expires_at: Utils.to_masto_date(end_time), + expired: expired, + multiple: multiple, + votes_count: votes_count, + options: options, + voted: voted, + emojis: build_emojis(object.data["emoji"]) + } + else + nil + end + end + def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do object = Object.normalize(activity) diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 5762e767b..cee6d8481 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -12,25 +12,27 @@ defmodule Pleroma.Web.MediaProxy do def url("/" <> _ = url), do: url def url(url) do - config = Application.get_env(:pleroma, :media_proxy, []) - domain = URI.parse(url).host + if !enabled?() or local?(url) or whitelisted?(url) do + url + else + encode_url(url) + end + end - cond do - !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) -> - url + defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false) - Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> - String.equivalent?(domain, pattern) - end) -> - url + defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) - true -> - encode_url(url) - end + defp whitelisted?(url) do + %{host: domain} = URI.parse(url) + + Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> + String.equivalent?(domain, pattern) + end) end def encode_url(url) do - secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] + secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base]) # Must preserve `%2F` for compatibility with S3 # https://git.pleroma.social/pleroma/pleroma/issues/580 @@ -52,7 +54,7 @@ defmodule Pleroma.Web.MediaProxy do end def decode_url(sig, url) do - secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] + secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base]) sig = Base.url_decode64!(sig, @base64_opts) local_sig = :crypto.hmac(:sha, secret, url) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 3bf2a0fbc..57f5b61bb 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -12,8 +12,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.Federator.Publisher - plug(Pleroma.Web.FederatingPlug) - def schemas(conn, _params) do response = %{ links: [ @@ -34,20 +32,15 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field # under software. def raw_nodeinfo do - instance = Application.get_env(:pleroma, :instance) - media_proxy = Application.get_env(:pleroma, :media_proxy) - suggestions = Application.get_env(:pleroma, :suggestions) - chat = Application.get_env(:pleroma, :chat) - gopher = Application.get_env(:pleroma, :gopher) stats = Stats.get_stats() mrf_simple = - Application.get_env(:pleroma, :mrf_simple) + Config.get(:mrf_simple) |> Enum.into(%{}) # This horror is needed to convert regex sigils to strings mrf_keyword = - Application.get_env(:pleroma, :mrf_keyword, []) + Config.get(:mrf_keyword, []) |> Enum.map(fn {key, value} -> {key, Enum.map(value, fn @@ -76,14 +69,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do MRF.get_policies() |> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end) - quarantined = Keyword.get(instance, :quarantined_instances) - - quarantined = - if is_list(quarantined) do - quarantined - else - [] - end + quarantined = Config.get([:instance, :quarantined_instances], []) staff_accounts = User.all_superusers() @@ -94,7 +80,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end) federation_response = - if Keyword.get(instance, :mrf_transparency) do + if Config.get([:instance, :mrf_transparency]) do %{ mrf_policies: mrf_policies, mrf_simple: mrf_simple, @@ -111,22 +97,23 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do "pleroma_api", "mastodon_api", "mastodon_api_streaming", - if Keyword.get(media_proxy, :enabled) do + "polls", + if Config.get([:media_proxy, :enabled]) do "media_proxy" end, - if Keyword.get(gopher, :enabled) do + if Config.get([:gopher, :enabled]) do "gopher" end, - if Keyword.get(chat, :enabled) do + if Config.get([:chat, :enabled]) do "chat" end, - if Keyword.get(suggestions, :enabled) do + if Config.get([:suggestions, :enabled]) do "suggestions" end, - if Keyword.get(instance, :allow_relay) do + if Config.get([:instance, :allow_relay]) do "relay" end, - if Keyword.get(instance, :safe_dm_mentions) do + if Config.get([:instance, :safe_dm_mentions]) do "safe_dm_mentions" end ] @@ -143,7 +130,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do inbound: [], outbound: [] }, - openRegistrations: Keyword.get(instance, :registrations_open), + openRegistrations: Config.get([:instance, :registrations_open]), usage: %{ users: %{ total: stats.user_count || 0 @@ -151,29 +138,30 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do localPosts: stats.status_count || 0 }, metadata: %{ - nodeName: Keyword.get(instance, :name), - nodeDescription: Keyword.get(instance, :description), - private: !Keyword.get(instance, :public, true), + nodeName: Config.get([:instance, :name]), + nodeDescription: Config.get([:instance, :description]), + private: !Config.get([:instance, :public], true), suggestions: %{ - enabled: Keyword.get(suggestions, :enabled, false), - thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""), - timeout: Keyword.get(suggestions, :timeout, 5000), - limit: Keyword.get(suggestions, :limit, 23), - web: Keyword.get(suggestions, :web, "") + enabled: Config.get([:suggestions, :enabled], false), + thirdPartyEngine: Config.get([:suggestions, :third_party_engine], ""), + timeout: Config.get([:suggestions, :timeout], 5000), + limit: Config.get([:suggestions, :limit], 23), + web: Config.get([:suggestions, :web], "") }, staffAccounts: staff_accounts, federation: federation_response, - postFormats: Keyword.get(instance, :allowed_post_formats), + pollLimits: Config.get([:instance, :poll_limits]), + postFormats: Config.get([:instance, :allowed_post_formats]), uploadLimits: %{ - general: Keyword.get(instance, :upload_limit), - avatar: Keyword.get(instance, :avatar_upload_limit), - banner: Keyword.get(instance, :banner_upload_limit), - background: Keyword.get(instance, :background_upload_limit) + general: Config.get([:instance, :upload_limit]), + avatar: Config.get([:instance, :avatar_upload_limit]), + banner: Config.get([:instance, :banner_upload_limit]), + background: Config.get([:instance, :background_upload_limit]) }, - accountActivationRequired: Keyword.get(instance, :account_activation_required, false), - invitesEnabled: Keyword.get(instance, :invites_enabled, false), + accountActivationRequired: Config.get([:instance, :account_activation_required], false), + invitesEnabled: Config.get([:instance, :invites_enabled], false), features: features, - restrictedNicknames: Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) + restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]) } } end diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 61515b31e..6ed089d84 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -3,13 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OStatus do - @httpoison Application.get_env(:pleroma, :httpoison) - import Ecto.Query import Pleroma.Web.XML require Logger alias Pleroma.Activity + alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -363,7 +362,7 @@ defmodule Pleroma.Web.OStatus do def fetch_activity_from_atom_url(url) do with true <- String.starts_with?(url, "http"), {:ok, %{body: body, status: code}} when code in 200..299 <- - @httpoison.get( + HTTP.get( url, [{:Accept, "application/atom+xml"}] ) do @@ -380,7 +379,7 @@ defmodule Pleroma.Web.OStatus do Logger.debug("Trying to fetch #{url}") with true <- String.starts_with?(url, "http"), - {:ok, %{body: body}} <- @httpoison.get(url, []), + {:ok, %{body: body}} <- HTTP.get(url, []), {:ok, atom_url} <- get_atom_url(body) do fetch_activity_from_atom_url(atom_url) else diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 62e8fa610..e4595800c 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -37,7 +37,10 @@ defmodule Pleroma.Web.RichMedia.Parser do try do {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options) - html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data() + html + |> maybe_parse() + |> clean_parsed_data() + |> check_parsed_data() rescue e -> {:error, "Parsing error: #{inspect(e)}"} diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 352268b96..e699f6ae2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -309,8 +309,6 @@ defmodule Pleroma.Web.Router do post("/conversations/:id/read", MastodonAPIController, :conversation_read) get("/endorsements", MastodonAPIController, :empty_array) - - get("/pleroma/flavour", MastodonAPIController, :get_flavour) end scope [] do @@ -335,6 +333,8 @@ defmodule Pleroma.Web.Router do put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status) delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status) + post("/polls/:id/votes", MastodonAPIController, :poll_vote) + post("/media", MastodonAPIController, :upload) put("/media/:id", MastodonAPIController, :update_media) @@ -350,8 +350,6 @@ defmodule Pleroma.Web.Router do put("/filters/:id", MastodonAPIController, :update_filter) delete("/filters/:id", MastodonAPIController, :delete_filter) - post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour) - get("/pleroma/mascot", MastodonAPIController, :get_mascot) put("/pleroma/mascot", MastodonAPIController, :set_mascot) @@ -426,6 +424,8 @@ defmodule Pleroma.Web.Router do get("/statuses/:id", MastodonAPIController, :get_status) get("/statuses/:id/context", MastodonAPIController, :get_context) + get("/polls/:id", MastodonAPIController, :get_poll) + get("/accounts/:id/statuses", MastodonAPIController, :user_statuses) get("/accounts/:id/followers", MastodonAPIController, :followers) get("/accounts/:id/following", MastodonAPIController, :following) diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 9fefdbe25..19e3ef401 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -5,11 +5,10 @@ defmodule Pleroma.Web.Salmon do @behaviour Pleroma.Web.Federator.Publisher - @httpoison Application.get_env(:pleroma, :httpoison) - use Bitwise alias Pleroma.Activity + alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.Keys alias Pleroma.User @@ -153,7 +152,7 @@ defmodule Pleroma.Web.Salmon do def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do with {:ok, %{status: code}} when code in 200..299 <- - @httpoison.post( + HTTP.post( url, feed, [{"Content-Type", "application/magic-envelope+xml"}] diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 3389c91cc..b3cf9ed11 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -4,7 +4,7 @@ <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" /> <title> - <%= Application.get_env(:pleroma, :instance)[:name] %> + <%= Pleroma.Config.get([:instance, :name]) %> </title> <style> body { @@ -63,13 +63,14 @@ .scopes-input { display: flex; + flex-direction: column; margin-top: 1em; text-align: left; color: #89898a; } .scopes-input label:first-child { - flex-basis: 40%; + height: 2em; } .scopes { @@ -80,13 +81,22 @@ } .scope { - flex-basis: 100%; display: flex; + flex-basis: 100%; height: 2em; align-items: center; } + .scope:before { + color: #b9b9ba; + content: "✔\fe0e"; + margin-left: 1em; + margin-right: 1em; + } + [type="checkbox"] + label { + display: none; + cursor: pointer; margin: 0.5em; } @@ -95,10 +105,12 @@ } [type="checkbox"] + label:before { + cursor: pointer; display: inline-block; color: white; background-color: #121a24; border: 4px solid #121a24; + box-shadow: 0px 0px 1px 0 #d8a070; box-sizing: border-box; width: 1.2em; height: 1.2em; @@ -128,7 +140,8 @@ border-radius: 4px; border: none; padding: 10px; - margin-top: 30px; + margin-top: 20px; + margin-bottom: 20px; text-transform: uppercase; font-size: 16px; box-shadow: 0px 0px 2px 0px black, @@ -147,8 +160,8 @@ box-sizing: border-box; width: 100%; background-color: #931014; + border: 1px solid #a06060; border-radius: 4px; - border: none; padding: 10px; margin-top: 20px; font-weight: 500; @@ -171,12 +184,27 @@ margin-top: 0 } - .scopes-input { - flex-direction: column; + .scope { + flex-basis: 0%; } - .scope { - flex-basis: 50%; + .scope:before { + content: ""; + margin-left: 0em; + margin-right: 1em; + } + + .scope:first-child:before { + margin-left: 1em; + content: "✔\fe0e"; + } + + .scope:after { + content: ","; + } + + .scope:last-child:after { + content: ""; } } .form-row { @@ -194,7 +222,7 @@ </head> <body> <div class="container"> - <h1><%= Application.get_env(:pleroma, :instance)[:name] %></h1> + <h1><%= Pleroma.Config.get([:instance, :name]) %></h1> <%= render @view_module, @view_template, assigns %> </div> </body> diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex index 5659c7828..3325beca1 100644 --- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex +++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex @@ -4,11 +4,11 @@ <meta charset='utf-8'> <meta content='width=device-width, initial-scale=1' name='viewport'> <title> -<%= Application.get_env(:pleroma, :instance)[:name] %> +<%= Pleroma.Config.get([:instance, :name]) %> </title> <link rel="icon" type="image/png" href="/favicon.png"/> <script crossorigin='anonymous' src="/packs/locales.js"></script> -<script crossorigin='anonymous' src="/packs/locales/<%= @flavour %>/en.js"></script> +<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script> <link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'> <link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'> @@ -19,10 +19,10 @@ <script src="/packs/core/common.js"></script> <link rel="stylesheet" media="all" href="/packs/core/common.css" /> -<script src="/packs/flavours/<%= @flavour %>/common.js"></script> -<link rel="stylesheet" media="all" href="/packs/flavours/<%= @flavour %>/common.css" /> +<script src="/packs/flavours/glitch/common.js"></script> +<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" /> -<script src="/packs/flavours/<%= @flavour %>/home.js"></script> +<script src="/packs/flavours/glitch/home.js"></script> </head> <body class='app-body no-reduce-motion system-font'> <div class='app-holder' data-props='{"locale":"en"}' id='mastodon'> 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 e6cfe108b..c9ec1ecbf 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 @@ -1,13 +1,19 @@ <div class="scopes-input"> - <%= label @form, :scope, "Permissions" %> - + <%= label @form, :scope, "The following permissions will be granted" %> <div class="scopes"> <%= for scope <- @available_scopes do %> <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> - <div class="scope"> + <%= if scope in @scopes do %> + <div class="scope"> + <%= 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) %> + <% end %> + </div> + <% else %> <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> - <%= label @form, :"scope_#{scope}", String.capitalize(scope) %> - </div> + <% end %> <% end %> </div> </div> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex index 4bcda7300..4a0718851 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex @@ -1,7 +1,9 @@ <h2>Sign in with external provider</h2> <%= form_for @conn, o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %> - <%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %> + <div style="display: none"> + <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> + </div> <%= hidden_input f, :client_id, value: @client_id %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %> 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 3e360a52c..b17142ff8 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 @@ -6,26 +6,38 @@ <% end %> <h2>OAuth Authorization</h2> - <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> -<div class="input"> - <%= label f, :name, "Name or email" %> - <%= text_input f, :name %> -</div> -<div class="input"> - <%= label f, :password, "Password" %> - <%= password_input f, :password %> -</div> -<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> +<%= if @params["registration"] in ["true", true] do %> + <h3>This is the first time you visit! Please enter your Pleroma handle.</h3> + <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p> + <div class="input"> + <%= label f, :nickname, "Pleroma Handle" %> + <%= text_input f, :nickname, placeholder: "lain" %> + </div> + <%= hidden_input f, :name, value: @params["name"] %> + <%= hidden_input f, :password, value: @params["password"] %> + <br> +<% else %> + <div class="input"> + <%= label f, :name, "Username" %> + <%= text_input f, :name %> + </div> + <div class="input"> + <%= label f, :password, "Password" %> + <%= password_input f, :password %> + </div> + <%= submit "Log In" %> + <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> +<% end %> <%= hidden_input f, :client_id, value: @client_id %> <%= hidden_input f, :response_type, value: @response_type %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %> <%= hidden_input f, :state, value: @state %> -<%= submit "Authorize" %> <% end %> <%= if Pleroma.Config.oauth_consumer_enabled?() do %> <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %> <% end %> + diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 31e86685a..1b6b33e69 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -728,7 +728,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn def only_if_public_instance(conn, _) do - if Keyword.get(Application.get_env(:pleroma, :instance), :public) do + if Pleroma.Config.get([:instance, :public]) do conn else conn diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index f0a4ddbd3..550f35f5f 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -121,6 +121,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do "tags" => user.tags } |> maybe_with_activation_status(user, for_user) + |> with_notification_settings(user, for_user) } |> maybe_with_user_settings(user, for_user) |> maybe_with_role(user, for_user) @@ -132,6 +133,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do end end + defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do + Map.put(data, "notification_settings", user.info.notification_settings) + end + + defp with_notification_settings(data, _, _), do: data + defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do Map.put(data, "deactivated", user.info.deactivated) end diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index c5b7d4acb..3fca72de8 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -3,8 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.WebFinger do - @httpoison Application.get_env(:pleroma, :httpoison) - + alias Pleroma.HTTP alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.Federator.Publisher @@ -176,11 +175,11 @@ defmodule Pleroma.Web.WebFinger do def find_lrdd_template(domain) do with {:ok, %{status: status, body: body}} when status in 200..299 <- - @httpoison.get("http://#{domain}/.well-known/host-meta", []) do + HTTP.get("http://#{domain}/.well-known/host-meta", []) do get_template_from_xml(body) else _ -> - with {:ok, %{body: body}} <- @httpoison.get("https://#{domain}/.well-known/host-meta", []) do + with {:ok, %{body: body}} <- HTTP.get("https://#{domain}/.well-known/host-meta", []) do get_template_from_xml(body) else e -> {:error, "Can't find LRDD template: #{inspect(e)}"} @@ -209,7 +208,7 @@ defmodule Pleroma.Web.WebFinger do end with response <- - @httpoison.get( + HTTP.get( address, Accept: "application/xrd+xml,application/jrd+json" ), diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex index 7ad0414ab..b61f388b8 100644 --- a/lib/pleroma/web/websub/websub.ex +++ b/lib/pleroma/web/websub/websub.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.Websub do alias Ecto.Changeset alias Pleroma.Activity + alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.Repo alias Pleroma.User @@ -24,9 +25,7 @@ defmodule Pleroma.Web.Websub do @behaviour Pleroma.Web.Federator.Publisher - @httpoison Application.get_env(:pleroma, :httpoison) - - def verify(subscription, getter \\ &@httpoison.get/3) do + def verify(subscription, getter \\ &HTTP.get/3) do challenge = Base.encode16(:crypto.strong_rand_bytes(8)) lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at) lease_seconds = lease_seconds |> to_string @@ -207,7 +206,7 @@ defmodule Pleroma.Web.Websub do requester.(subscription) end - def gather_feed_data(topic, getter \\ &@httpoison.get/1) do + def gather_feed_data(topic, getter \\ &HTTP.get/1) do with {:ok, response} <- getter.(topic), status when status in 200..299 <- response.status, body <- response.body, @@ -236,7 +235,7 @@ defmodule Pleroma.Web.Websub do end end - def request_subscription(websub, poster \\ &@httpoison.post/3, timeout \\ 10_000) do + def request_subscription(websub, poster \\ &HTTP.post/3, timeout \\ 10_000) do data = [ "hub.mode": "subscribe", "hub.topic": websub.topic, @@ -294,7 +293,7 @@ defmodule Pleroma.Web.Websub do Logger.info(fn -> "Pushing #{topic} to #{callback}" end) with {:ok, %{status: code}} when code in 200..299 <- - @httpoison.post( + HTTP.post( callback, xml, [ |