diff options
author | Alexander Strizhakov <alex.strizhakov@gmail.com> | 2020-02-29 11:34:50 +0300 |
---|---|---|
committer | Alexander Strizhakov <alex.strizhakov@gmail.com> | 2020-02-29 11:34:50 +0300 |
commit | 814b275af7748df6bd11dfc6be1b4efce8d5ae70 (patch) | |
tree | e18b30dfa6eb020e593dbe1cc0e649da3e6ed394 /lib | |
parent | 6b012ddd69aec0f85c22ad91dbb76e05f2edaf58 (diff) | |
parent | aad057a2d6baaa342f47f55d330f0731277dac06 (diff) | |
download | pleroma-814b275af7748df6bd11dfc6be1b4efce8d5ae70.tar.gz |
Merge branch 'develop' into gun
Diffstat (limited to 'lib')
29 files changed, 350 insertions, 75 deletions
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index b4e8d3a0b..adac47c86 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -185,11 +185,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}") - {:ok, _} = - :zip.unzip( - binary_archive, - cwd: tmp_pack_dir - ) + {:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir)) emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts) diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 9af6cda30..54d34e42f 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -6,6 +6,8 @@ defmodule Mix.Tasks.Pleroma.Instance do use Mix.Task import Mix.Pleroma + alias Pleroma.Config + @shortdoc "Manages Pleroma instance" @moduledoc File.read!("docs/administration/CLI_tasks/instance.md") @@ -63,7 +65,8 @@ defmodule Mix.Tasks.Pleroma.Instance do get_option( options, :instance_name, - "What is the name of your instance? (e.g. Pleroma/Soykaf)" + "What is the name of your instance? (e.g. The Corndog Emporium)", + domain ) email = get_option(options, :admin_email, "What is your admin email address?") @@ -153,6 +156,8 @@ defmodule Mix.Tasks.Pleroma.Instance do Pleroma.Config.get([:instance, :static_dir]) ) + Config.put([:instance, :static_dir], static_dir) + secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8) @@ -202,8 +207,14 @@ defmodule Mix.Tasks.Pleroma.Instance do write_robots_txt(indexable, template_dir) shell_info( - "\n All files successfully written! Refer to the installation instructions for your platform for next steps" + "\n All files successfully written! Refer to the installation instructions for your platform for next steps." ) + + if db_configurable? do + shell_info( + " Please transfer your config to the database after running database migrations. Refer to \"Transfering the config to/from the database\" section of the docs for more information." + ) + end else shell_error( "The task would have overwritten the following files:\n" <> diff --git a/lib/mix/tasks/pleroma/refresh_counter_cache.ex b/lib/mix/tasks/pleroma/refresh_counter_cache.ex new file mode 100644 index 000000000..bc2571efd --- /dev/null +++ b/lib/mix/tasks/pleroma/refresh_counter_cache.ex @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.RefreshCounterCache do + @shortdoc "Refreshes counter cache" + + use Mix.Task + + alias Pleroma.Activity + alias Pleroma.CounterCache + alias Pleroma.Repo + + require Logger + import Ecto.Query + + def run([]) do + Mix.Pleroma.start_pleroma() + + ["public", "unlisted", "private", "direct"] + |> Enum.each(fn visibility -> + count = status_visibility_count_query(visibility) + name = "status_visibility_#{visibility}" + CounterCache.set(name, count) + Mix.Pleroma.shell_info("Set #{name} to #{count}") + end) + + Mix.Pleroma.shell_info("Done") + end + + defp status_visibility_count_query(visibility) do + Activity + |> where( + [a], + fragment( + "activity_visibility(?, ?, ?) = ?", + a.actor, + a.recipients, + a.data, + ^visibility + ) + ) + |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data)) + |> Repo.aggregate(:count, :id, timeout: :timer.minutes(30)) + end +end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 85c9e4954..ba10a705a 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -100,8 +100,7 @@ defmodule Mix.Tasks.Pleroma.User do User.perform(:delete, user) shell_info("User #{nickname} deleted.") else - _ -> - shell_error("No local user #{nickname}") + _ -> shell_error("No local user #{nickname}") end end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 72e2256ea..7fb1f913b 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -310,7 +310,7 @@ defmodule Pleroma.Activity do def restrict_deactivated_users(query) do deactivated_users = - from(u in User.Query.build(deactivated: true), select: u.ap_id) + from(u in User.Query.build(%{deactivated: true}), select: u.ap_id) |> Repo.all() Activity.Queries.exclude_authors(query, deactivated_users) diff --git a/lib/pleroma/activity/queries.ex b/lib/pleroma/activity/queries.ex index 79f305201..8d08d81ca 100644 --- a/lib/pleroma/activity/queries.ex +++ b/lib/pleroma/activity/queries.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Activity.Queries do Contains queries for Activity. """ - import Ecto.Query, only: [from: 2] + import Ecto.Query, only: [from: 2, where: 3] @type query :: Ecto.Queryable.t() | Activity.t() @@ -30,7 +30,7 @@ defmodule Pleroma.Activity.Queries do ) end - @spec by_author(query, String.t()) :: query + @spec by_author(query, User.t()) :: query def by_author(query \\ Activity, %User{ap_id: ap_id}) do from(a in query, where: a.actor == ^ap_id) end @@ -63,6 +63,22 @@ defmodule Pleroma.Activity.Queries do ) end + @spec by_object_in_reply_to_id(query, String.t(), keyword()) :: query + def by_object_in_reply_to_id(query, in_reply_to_id, opts \\ []) do + query = + if opts[:skip_preloading] do + Activity.with_joined_object(query) + else + Activity.with_preloaded_object(query) + end + + where( + query, + [activity, object: o], + fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(in_reply_to_id)) + ) + end + @spec by_type(query, String.t()) :: query def by_type(query \\ Activity, activity_type) do from( diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index c2765a5b8..cf75c3adc 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha do @@ -50,7 +50,7 @@ defmodule Pleroma.Captcha do token = new_captcha[:token] secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") - # Basicallty copy what Phoenix.Token does here, add the time to + # Basically copy what Phoenix.Token does here, add the time to # the actual data and make it a binary to then encrypt it encrypted_captcha_answer = %{ @@ -62,7 +62,7 @@ defmodule Pleroma.Captcha do { :reply, - # Repalce the answer with the encrypted answer + # Replace the answer with the encrypted answer %{new_captcha | answer_data: encrypted_captcha_answer}, state } @@ -82,7 +82,8 @@ defmodule Pleroma.Captcha do valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid) result = - with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), + with false <- is_nil(answer_data), + {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do try do if DateTime.before?(at, valid_if_after), diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex index 5306fe1aa..2c8db2c30 100644 --- a/lib/pleroma/captcha/native.ex +++ b/lib/pleroma/captcha/native.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Captcha.Native do @impl Service def new do case Captcha.get() do - {:timeout} -> - %{error: dgettext("errors", "Captcha timeout")} + :error -> + %{error: dgettext("errors", "Captcha error")} {:ok, answer_data, img_binary} -> %{ diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index e5d28ebff..d4b255537 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -133,10 +133,8 @@ defmodule Pleroma.Conversation.Participation do [user.id | user_ids] |> Enum.uniq() |> Enum.reduce([], fn user_id, acc -> - case FlakeId.Ecto.CompatType.dump(user_id) do - {:ok, user_id} -> [user_id | acc] - _ -> acc - end + {:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id) + [user_id | acc] end) conversation_subquery = diff --git a/lib/pleroma/counter_cache.ex b/lib/pleroma/counter_cache.ex new file mode 100644 index 000000000..8e868e956 --- /dev/null +++ b/lib/pleroma/counter_cache.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.CounterCache do + alias Pleroma.CounterCache + alias Pleroma.Repo + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query + + schema "counter_cache" do + field(:name, :string) + field(:count, :integer) + end + + def changeset(struct, params) do + struct + |> cast(params, [:name, :count]) + |> validate_required([:name]) + |> unique_constraint(:name) + end + + def get_as_map(names) when is_list(names) do + CounterCache + |> where([cc], cc.name in ^names) + |> Repo.all() + |> Enum.group_by(& &1.name, & &1.count) + |> Map.new(fn {k, v} -> {k, hd(v)} end) + end + + def set(name, count) do + %CounterCache{} + |> changeset(%{"name" => name, "count" => count}) + |> Repo.insert( + on_conflict: [set: [count: count]], + returning: true, + conflict_target: :name + ) + end +end diff --git a/lib/pleroma/mime.ex b/lib/pleroma/mime.ex index 36771533f..08f96f7bf 100644 --- a/lib/pleroma/mime.ex +++ b/lib/pleroma/mime.ex @@ -9,7 +9,7 @@ defmodule Pleroma.MIME do @default "application/octet-stream" @read_bytes 35 - @spec file_mime_type(String.t()) :: + @spec file_mime_type(String.t(), String.t()) :: {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error def file_mime_type(path, filename) do with {:ok, content_type} <- file_mime_type(path), diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 52556bf31..f316f8b36 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -301,4 +301,26 @@ defmodule Pleroma.Object do def local?(%Object{data: %{"id" => id}}) do String.starts_with?(id, Pleroma.Web.base_url() <> "/") end + + def replies(object, opts \\ []) do + object = Object.normalize(object) + + query = + Object + |> where( + [o], + fragment("(?)->>'inReplyTo' = ?", o.data, ^object.data["id"]) + ) + |> order_by([o], asc: o.id) + + if opts[:self_only] do + actor = object.data["actor"] + where(query, [o], fragment("(?)->>'actor' = ?", o.data, ^actor)) + else + query + end + end + + def self_replies(object, opts \\ []), + do: replies(object, Keyword.put(opts, :self_only, true)) end diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 25aa32f60..9efa50edb 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -39,15 +39,8 @@ defmodule Pleroma.Object.Containment do defp compare_uris(_, %URI{scheme: "tag"}), do: :ok end - defp compare_uris(%URI{} = id_uri, %URI{} = other_uri) do - if id_uri.host == other_uri.host do - :ok - else - :error - end - end - - defp compare_uris(_, _), do: :error + defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok + defp compare_uris(_id_uri, _other_uri), do: :error @doc """ Checks that an imported AP object's actor matches the domain it came from. diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 5e9bf1574..d7fd35f1d 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.Signature alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.Federator require Logger require Pleroma.Constants @@ -59,20 +60,23 @@ defmodule Pleroma.Object.Fetcher do end end - # TODO: - # This will create a Create activity, which we need internally at the moment. + # Note: will create a Create activity, which we need internally at the moment. def fetch_object_from_id(id, options \\ []) do - with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, - {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, - {:normalize, nil} <- {:normalize, Object.normalize(data, false)}, + with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, + {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])}, + {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, + {_, nil} <- {:normalize, Object.normalize(data, false)}, params <- prepare_activity_params(data), - {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)}, - {:transmogrifier, {:ok, activity}} <- + {_, :ok} <- {:containment, Containment.contain_origin(id, params)}, + {_, {:ok, activity}} <- {:transmogrifier, Transmogrifier.handle_incoming(params, options)}, - {:object, _data, %Object{} = object} <- + {_, _data, %Object{} = object} <- {:object, data, Object.normalize(activity, false)} do {:ok, object} else + {:allowed_depth, false} -> + {:error, "Max thread distance exceeded."} + {:containment, _} -> {:error, "Object containment failed."} diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex index cf590fb01..771a06e32 100644 --- a/lib/pleroma/stats.ex +++ b/lib/pleroma/stats.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Stats do import Ecto.Query + alias Pleroma.CounterCache alias Pleroma.Repo alias Pleroma.User @@ -96,4 +97,21 @@ defmodule Pleroma.Stats do } } end + + def get_status_visibility_count do + counter_cache = + CounterCache.get_as_map([ + "status_visibility_public", + "status_visibility_private", + "status_visibility_unlisted", + "status_visibility_direct" + ]) + + %{ + public: counter_cache["status_visibility_public"] || 0, + unlisted: counter_cache["status_visibility_unlisted"] || 0, + private: counter_cache["status_visibility_private"] || 0, + direct: counter_cache["status_visibility_direct"] || 0 + } + end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 5ea36fea3..56e599ecc 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -853,14 +853,14 @@ defmodule Pleroma.User do @spec get_followers_query(User.t()) :: Ecto.Query.t() def get_followers_query(user), do: get_followers_query(user, nil) - @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} + @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())} def get_followers(user, page \\ nil) do user |> get_followers_query(page) |> Repo.all() end - @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} + @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())} def get_external_followers(user, page \\ nil) do user |> get_followers_query(page) @@ -1304,7 +1304,6 @@ defmodule Pleroma.User do Repo.delete(user) end - @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:fetch_initial_posts, %User{} = user) do pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) @@ -1336,7 +1335,6 @@ defmodule Pleroma.User do ) end - @spec perform(atom(), User.t(), list()) :: list() | {:error, any()} def perform(:follow_import, %User{} = follower, followed_identifiers) when is_list(followed_identifiers) do Enum.map( diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index 364bc1c89..4358907cb 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -48,7 +48,7 @@ defmodule Pleroma.User.Query do followers: User.t(), friends: User.t(), recipients_from_activity: [String.t()], - nickname: [String.t()], + nickname: [String.t()] | String.t(), ap_id: [String.t()], order_by: term(), select: term(), diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index 6b55df483..1cfecef83 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -33,9 +33,15 @@ defmodule Pleroma.User.Search do # Strip the beginning @ off if there is a query query_string = String.trim_leading(query_string, "@") - with [name, domain] <- String.split(query_string, "@"), - formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") do - name <> "@" <> to_string(:idna.encode(formatted_domain)) + with [name, domain] <- String.split(query_string, "@") do + encoded_domain = + domain + |> String.replace(~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") + |> String.to_charlist() + |> :idna.encode() + |> to_string() + + name <> "@" <> encoded_domain else _ -> query_string end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 5c436941a..12695b3f9 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -770,13 +770,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Enum.reverse() end - def fetch_instance_activities(params) do + def fetch_statuses(reading_user, params) do params = params |> Map.put("type", ["Create", "Announce"]) - |> Map.put("instance", params["instance"]) - fetch_activities([Pleroma.Constants.as_public()], params, :offset) + recipients = + user_activities_recipients(%{ + "godmode" => params["godmode"], + "reading_user" => reading_user + }) + + fetch_activities(recipients, params, :offset) |> Enum.reverse() end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a72d8430f..3afc82345 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -156,10 +156,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do when not is_nil(in_reply_to) do in_reply_to_id = prepare_in_reply_to(in_reply_to) object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) + depth = (options[:depth] || 0) + 1 - if Federator.allowed_incoming_reply_depth?(options[:depth]) do + if Federator.allowed_thread_distance?(depth) do with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options), - %Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do + %Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do object |> Map.put("inReplyTo", replied_object.data["id"]) |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) @@ -312,7 +313,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) when is_binary(reply_id) do - with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), + with true <- Federator.allowed_thread_distance?(options[:depth]), {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do Map.put(object, "type", "Answer") else @@ -406,8 +407,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do with nil <- Activity.get_create_by_object_ap_id(object["id"]), {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do - options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) - object = fix_object(data["object"], options) + object = fix_object(object, options) params = %{ to: data["to"], @@ -424,7 +424,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do ]) } - ActivityPub.create(params) + with {:ok, created_activity} <- ActivityPub.create(params) do + reply_depth = (options[:depth] || 0) + 1 + + if Federator.allowed_thread_distance?(reply_depth) do + for reply_id <- replies(object) do + Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ + "id" => reply_id, + "depth" => reply_depth + }) + end + end + + {:ok, created_activity} + end else %Activity{} = activity -> {:ok, activity} _e -> :error @@ -442,7 +455,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_addressing with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do - options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) + reply_depth = (options[:depth] || 0) + 1 + options = Keyword.put(options, :depth, reply_depth) object = fix_object(object, options) params = %{ @@ -903,6 +917,50 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def set_reply_to_uri(obj), do: obj + @doc """ + Serialized Mastodon-compatible `replies` collection containing _self-replies_. + Based on Mastodon's ActivityPub::NoteSerializer#replies. + """ + def set_replies(obj_data) do + replies_uris = + with limit when limit > 0 <- + Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0), + %Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do + object + |> Object.self_replies() + |> select([o], fragment("?->>'id'", o.data)) + |> limit(^limit) + |> Repo.all() + else + _ -> [] + end + + set_replies(obj_data, replies_uris) + end + + defp set_replies(obj, []) do + obj + end + + defp set_replies(obj, replies_uris) do + replies_collection = %{ + "type" => "Collection", + "items" => replies_uris + } + + Map.merge(obj, %{"replies" => replies_collection}) + end + + def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do + items + end + + def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do + items + end + + def replies(_), do: [] + # Prepares the object of an outgoing create activity. def prepare_object(object) do object @@ -914,6 +972,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> prepare_attachments |> set_conversation |> set_reply_to_uri + |> set_replies |> strip_internal_fields |> strip_internal_tags |> set_type diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 10ce5eee8..50e076ca4 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -45,8 +45,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do Map.put(params, "actor", get_ap_id(params["actor"])) end - @spec determine_explicit_mentions(map()) :: map() - def determine_explicit_mentions(%{"tag" => tag} = _) when is_list(tag) do + @spec determine_explicit_mentions(map()) :: [any] + def determine_explicit_mentions(%{"tag" => tag}) when is_list(tag) do Enum.flat_map(tag, fn %{"type" => "Mention", "href" => href} -> [href] _ -> [] @@ -427,7 +427,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do @doc """ Updates a follow activity's state (for locked accounts). """ - @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity} | {:error, any()} + @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity | nil} def update_follow_state_for_all( %Activity{data: %{"actor" => actor, "object" => object}} = activity, state diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 67222ebae..8804343b9 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.ModerationLog alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.ReportNote + alias Pleroma.Stats alias Pleroma.User alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.ActivityPub @@ -98,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["read"], admin: true} - when action in [:config_show, :list_log] + when action in [:config_show, :list_log, :stats] ) plug( @@ -243,13 +244,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def list_instance_statuses(conn, %{"instance" => instance} = params) do + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true {page, page_size} = page_params(params) activities = - ActivityPub.fetch_instance_activities(%{ + ActivityPub.fetch_statuses(nil, %{ "instance" => instance, "limit" => page_size, - "offset" => (page - 1) * page_size + "offset" => (page - 1) * page_size, + "exclude_reblogs" => !with_reblogs && "true" }) conn @@ -258,6 +261,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end def list_user_statuses(conn, %{"nickname" => nickname} = params) do + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true godmode = params["godmode"] == "true" || params["godmode"] == true with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do @@ -266,7 +270,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do activities = ActivityPub.fetch_user_activities(user, nil, %{ "limit" => page_size, - "godmode" => godmode + "godmode" => godmode, + "exclude_reblogs" => !with_reblogs && "true" }) conn @@ -740,6 +745,24 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end + def list_statuses(%{assigns: %{user: admin}} = conn, params) do + godmode = params["godmode"] == "true" || params["godmode"] == true + local_only = params["local_only"] == "true" || params["local_only"] == true + {page, page_size} = page_params(params) + + activities = + ActivityPub.fetch_statuses(admin, %{ + "godmode" => godmode, + "local_only" => local_only, + "limit" => page_size, + "offset" => (page - 1) * page_size + }) + + conn + |> put_view(Pleroma.Web.AdminAPI.StatusView) + |> render("index.json", %{activities: activities, as: :activity}) + end + def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) @@ -953,6 +976,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> json("") end + def stats(conn, _) do + count = Stats.get_status_visibility_count() + + conn + |> json(%{"status_visibility" => count}) + end + def errors(conn, {:error, :not_found}) do conn |> put_status(:not_found) diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex index ed919833e..778cf4c36 100644 --- a/lib/pleroma/web/admin_api/search.ex +++ b/lib/pleroma/web/admin_api/search.ex @@ -18,7 +18,11 @@ defmodule Pleroma.Web.AdminAPI.Search do @spec user(map()) :: {:ok, [User.t()], pos_integer()} def user(params \\ %{}) do - query = User.Query.build(params) |> order_by([u], u.nickname) + query = + params + |> Map.drop([:page, :page_size]) + |> User.Query.build() + |> order_by([u], u.nickname) paginated_query = User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size) diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex index 6f2b2b09c..8ae8a7afe 100644 --- a/lib/pleroma/web/admin_api/views/status_view.ex +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do alias Pleroma.User def render("index.json", opts) do - render_many(opts.activities, __MODULE__, "show.json", opts) + safe_render_many(opts.activities, __MODULE__, "show.json", opts) end def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index f506a7d24..013fb5b70 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -15,13 +15,19 @@ defmodule Pleroma.Web.Federator do require Logger - @doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)" + @doc """ + Returns `true` if the distance to target object does not exceed max configured value. + Serves to prevent fetching of very long threads, especially useful on smaller instances. + Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161). + Applies to fetching of both ancestor (reply-to) and child (reply) objects. + """ # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength - def allowed_incoming_reply_depth?(depth) do - max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) + def allowed_thread_distance?(distance) do + max_distance = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) - if max_replies_depth do - (depth || 1) <= max_replies_depth + if max_distance && max_distance >= 0 do + # Default depth is 0 (an object has zero distance from itself in its thread) + (distance || 0) <= max_distance else true end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 333012920..947edd8b7 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -92,9 +92,9 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do openRegistrations: Config.get([:instance, :registrations_open]), usage: %{ users: %{ - total: stats.user_count || 0 + total: Map.get(stats, :user_count, 0) }, - localPosts: stats.status_count || 0 + localPosts: Map.get(stats, :status_count, 0) }, metadata: %{ nodeName: Config.get([:instance, :name]), diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index a2f6d2287..03e95e020 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -323,7 +323,7 @@ keeping it in cache for #{div(cache_ms, 1000)}s") {:ok, _} -> conn |> json("ok") - {:error, _} -> + {:error, _, _} -> conn |> put_status(:internal_server_error) |> json(%{error: "Couldn't delete the pack #{name}"}) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 9bfe86704..103c638b4 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -192,6 +192,7 @@ defmodule Pleroma.Web.Router do put("/statuses/:id", AdminAPIController, :status_update) delete("/statuses/:id", AdminAPIController, :status_delete) + get("/statuses", AdminAPIController, :list_statuses) get("/config", AdminAPIController, :config_show) post("/config", AdminAPIController, :config_update) @@ -201,6 +202,7 @@ defmodule Pleroma.Web.Router do get("/moderation_log", AdminAPIController, :list_log) post("/reload_emoji", AdminAPIController, :reload_emoji) + get("/stats", AdminAPIController, :stats) end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex new file mode 100644 index 000000000..e860ca869 --- /dev/null +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.RemoteFetcherWorker do + alias Pleroma.Object.Fetcher + + use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher" + + @impl Oban.Worker + def perform( + %{ + "op" => "fetch_remote", + "id" => id + } = args, + _job + ) do + {:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"]) + end +end |