From 19eb8264249ff41a516834fc0728cf652cafb6b9 Mon Sep 17 00:00:00 2001 From: foxing Date: Sat, 11 Mar 2023 03:26:48 +0000 Subject: [PATCH 01/22] Show bubble_timeline in the api if any instances are set in it, do not show if none are set --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 2717da99d..d06c80a12 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -67,6 +67,9 @@ def features do "pleroma:api/v1/notifications:include_types_filter", "quote_posting", "editing", + if Config.get([:instance, :local_bubble], []) != [] do + "bubble_timeline" + end, if Config.get([:media_proxy, :enabled]) do "media_proxy" end, From 0c77be9308102cb2e4710fbad02035e9dc7125c3 Mon Sep 17 00:00:00 2001 From: flisk Date: Sun, 12 Mar 2023 18:14:05 +0100 Subject: [PATCH 02/22] don't crash on malformed avatar and banner values weird values in href will cause base64 encoding to fail later down the line, so let's make sure the value we're passing on is somewhat sane, or at the very least a binary this fixes #482 --- lib/pleroma/user.ex | 24 ++++++++++++------------ test/pleroma/user_test.exs | 10 ++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f94202af5..480521984 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -366,21 +366,21 @@ def invisible?(%User{invisible: true}), do: true def invisible?(_), do: false def avatar_url(user, options \\ []) do - case user.avatar do - %{"url" => [%{"href" => href} | _]} -> - href - - _ -> - unless options[:no_default] do - Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png") - end - end + default = Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png") + do_optional_url(user.avatar, default, options) end def banner_url(user, options \\ []) do - case user.banner do - %{"url" => [%{"href" => href} | _]} -> href - _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png" + do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options) + end + + defp do_optional_url(field, default, options \\ []) do + case field do + %{"url" => [%{"href" => href} | _]} when is_binary(href) -> + href + + _ -> + unless options[:no_default], do: default end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index a590946c2..12ccc6bf4 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -2509,6 +2509,16 @@ test "avatar fallback" do assert User.avatar_url(user, no_default: true) == nil end + test "avatar object with nil in href" do + user = insert(:user, avatar: %{"url" => [%{"href" => nil}]}) + assert User.avatar_url(user) != nil + end + + test "banner object with nil in href" do + user = insert(:user, banner: %{"url" => [%{"href" => nil}]}) + assert User.banner_url(user) != nil + end + test "get_host/1" do user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain") assert User.get_host(user) == "lain.com" From 3f76de76dac266ea2909dbd1dc88a9533d972952 Mon Sep 17 00:00:00 2001 From: foxing Date: Sun, 12 Mar 2023 19:13:56 +0000 Subject: [PATCH 03/22] Apply Patch --- lib/pleroma/web/activity_pub/activity_pub.ex | 11 +++++++- .../web/activity_pub/activity_pub_test.exs | 8 ++++++ .../controllers/media_controller_test.exs | 17 +++++++++++ .../mastodon_api/update_credentials_test.exs | 28 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8e55df0d8..649bf9095 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1502,13 +1502,22 @@ def fetch_activities_bounded( @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} def upload(file, opts \\ []) do - with {:ok, data} <- Upload.store(file, opts) do + with {:ok, data} <- Upload.store(sanitize_upload_file(file), opts) do obj_data = Maps.put_if_present(data, "actor", opts[:actor]) Repo.insert(%Object{data: obj_data}) end end + defp sanitize_upload_file(%Plug.Upload{filename: filename} = upload) when is_binary(filename) do + %Plug.Upload{ + upload + | filename: Path.basename(filename) + } + end + + defp sanitize_upload_file(upload), do: upload + @spec get_actor_url(any()) :: binary() | nil defp get_actor_url(url) when is_binary(url), do: url defp get_actor_url(%{"href" => href}) when is_binary(href), do: href diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 17c52fc91..e95e4490a 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1303,6 +1303,14 @@ test "returns reblogs for users for whom reblogs have not been muted" do %{test_file: test_file} end + test "strips / from filename", %{test_file: file} do + file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"} + {:ok, %Object{} = object} = ActivityPub.upload(file) + [%{"href" => href}] = object.data["url"] + assert Regex.match?(~r"/bad.jpg$", href) + refute Regex.match?(~r"/nested/", href) + end + test "sets a description if given", %{test_file: file} do {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file") assert object.data["name"] == "a cool file" diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index 50b9febea..7ff8cff6b 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -124,6 +124,23 @@ test "/api/v2/media, upload_limit", %{conn: conn, user: user} do assert :ok == File.rm(Path.absname("test/tmp/large_binary.data")) end + + test "Do not allow nested filename", %{conn: conn, image: image} do + image = %Plug.Upload{ + image + | filename: "../../../../../nested/file.jpg" + } + + desc = "Description of the image" + + media = + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v1/media", %{"file" => image, "description" => desc}) + |> json_response_and_validate_schema(:ok) + + refute Regex.match?(~r"/nested/", media["url"]) + end end describe "Update media description" do diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index e9b8825bf..4aec31eac 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -396,6 +396,34 @@ test "updates the user's background, upload_limit, returns a HTTP 413", %{ assert :ok == File.rm(Path.absname("test/tmp/large_binary.data")) end + test "Strip / from upload files", %{user: user, conn: conn} do + new_image = %Plug.Upload{ + content_type: "image/jpeg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "../../../../nested/an_image.jpg" + } + + assert user.avatar == %{} + + res = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "avatar" => new_image, + "header" => new_image, + "pleroma_background_image" => new_image + }) + + assert user_response = json_response_and_validate_schema(res, 200) + assert user_response["avatar"] + assert user_response["header"] + assert user_response["pleroma"]["background_image"] + refute Regex.match?(~r"/nested/", user_response["avatar"]) + refute Regex.match?(~r"/nested/", user_response["header"]) + refute Regex.match?(~r"/nested/", user_response["pleroma"]["background_image"]) + + user = User.get_by_id(user.id) + refute user.avatar == %{} + end + test "requires 'write:accounts' permission" do token1 = insert(:oauth_token, scopes: ["read"]) token2 = insert(:oauth_token, scopes: ["write", "follow"]) From ba635e97c8ddcf92946e3d221dbf813189d21ab8 Mon Sep 17 00:00:00 2001 From: foxing Date: Mon, 13 Mar 2023 03:40:20 +0000 Subject: [PATCH 04/22] Use enum empty instead --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index d06c80a12..2b5354873 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -67,7 +67,7 @@ def features do "pleroma:api/v1/notifications:include_types_filter", "quote_posting", "editing", - if Config.get([:instance, :local_bubble], []) != [] do + if !Enum.empty?(Config.get([:instance, :local_bubble], [])) do "bubble_timeline" end, if Config.get([:media_proxy, :enabled]) do From 9464d505628d9f9dc20e54055d8853bea1d6722b Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:13:18 +0000 Subject: [PATCH 05/22] Add publicTimelineVisibility to nodeinfo --- .../web/nodeinfo/nodeinfo_controller.ex | 6 +++- test/pleroma/web/node_info_test.exs | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index a0dee7c6b..9a76574d5 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -89,7 +89,11 @@ def raw_nodeinfo do features: features, restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), - localBubbleInstances: Config.get([:instance, :local_bubble], []) + localBubbleInstances: Config.get([:instance, :local_bubble], []), + publicTimelineVisibility: %{ + federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), + local: !Config.restrict_unauthenticated_access?(:timelines, :local) + } } } end diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs index 05a078266..5f001be85 100644 --- a/test/pleroma/web/node_info_test.exs +++ b/test/pleroma/web/node_info_test.exs @@ -292,4 +292,34 @@ test "shows extra information in the mrf_simple_info field for relevant entries" assert response["metadata"]["federation"]["mrf_simple_info"] == expected_config end end + + describe "public timeline visibility" do + test "shows public timeline visibility", %{conn: conn} do + clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: false}) + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["publicTimelineVisibility"]["local"] == true + assert response["metadata"]["publicTimelineVisibility"]["federated"] == true + + clear_config([:restrict_unauthenticated, :timelines], %{local: true, federated: false}) + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + assert response["metadata"]["publicTimelineVisibility"]["local"] == false + assert response["metadata"]["publicTimelineVisibility"]["federated"] == true + + clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: true}) + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["publicTimelineVisibility"]["local"] == true + assert response["metadata"]["publicTimelineVisibility"]["federated"] == false + end + end end From 2c9e02429aaab2b9080b75a193142a4dd191631e Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:19:52 +0000 Subject: [PATCH 06/22] mix format --- test/pleroma/web/node_info_test.exs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs index 5f001be85..ff14db460 100644 --- a/test/pleroma/web/node_info_test.exs +++ b/test/pleroma/web/node_info_test.exs @@ -296,6 +296,7 @@ test "shows extra information in the mrf_simple_info field for relevant entries" describe "public timeline visibility" do test "shows public timeline visibility", %{conn: conn} do clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: false}) + response = conn |> get("/nodeinfo/2.1.json") @@ -305,14 +306,17 @@ test "shows public timeline visibility", %{conn: conn} do assert response["metadata"]["publicTimelineVisibility"]["federated"] == true clear_config([:restrict_unauthenticated, :timelines], %{local: true, federated: false}) + response = conn |> get("/nodeinfo/2.1.json") |> json_response(:ok) + assert response["metadata"]["publicTimelineVisibility"]["local"] == false assert response["metadata"]["publicTimelineVisibility"]["federated"] == true clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: true}) + response = conn |> get("/nodeinfo/2.1.json") From 86a5cf3c822e8566925564974d3849039e3b392f Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:20:32 +0000 Subject: [PATCH 07/22] Changelog entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d620c4f3b..c3b6f775a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +## Added +- Nodeinfo keys for unauthenticated timeline visibility + ## 2023.03 ## Fixed From fe7045632b77576b727281b0fa7a8d87b9b9b44a Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:59:58 +0000 Subject: [PATCH 08/22] also put publicVisibility in preloaded nodeinfo --- lib/pleroma/web/nodeinfo/nodeinfo.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index bf0d65f45..14e39e6b3 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -71,7 +71,11 @@ def get_nodeinfo("2.0") do restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), privilegedStaff: Config.get([:instance, :privileged_staff]), - localBubbleInstances: Config.get([:instance, :local_bubble], []) + localBubbleInstances: Config.get([:instance, :local_bubble], []), + publicTimelineVisibility: %{ + federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), + local: !Config.restrict_unauthenticated_access?(:timelines, :local) + } } } end From 4a5164be9301deb04506bda8dfee5a5bcf309960 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:53:38 -0400 Subject: [PATCH 09/22] Update required elixir version in mix.exs to 1.14 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 098ea15b0..aa1dde667 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ def project do [ app: :pleroma, version: version("3.7.1"), - elixir: "~> 1.12", + elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix] ++ Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], From f22bba6359d14dcd7572e504e1541e28b240347a Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:54:15 -0400 Subject: [PATCH 10/22] Update elixir version in elixir_buildpack.config --- elixir_buildpack.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elixir_buildpack.config b/elixir_buildpack.config index 946408c12..ee9e051a6 100644 --- a/elixir_buildpack.config +++ b/elixir_buildpack.config @@ -1,2 +1,2 @@ -elixir_version=1.9.4 -erlang_version=22.3.4.1 +elixir_version=1.14.3 +erlang_version=25.3 From 3c30666d3f422e6b94e2b81744c6b305f4699aa3 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:54:38 -0400 Subject: [PATCH 11/22] Update elixir and erlang versions in docs --- docs/docs/installation/generic_dependencies.include | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include index 68c61129a..d8cf9f9da 100644 --- a/docs/docs/installation/generic_dependencies.include +++ b/docs/docs/installation/generic_dependencies.include @@ -1,8 +1,8 @@ ## Required dependencies * PostgreSQL 9.6+ -* Elixir 1.12+ (1.13+ recommended) -* Erlang OTP 22.2+ +* Elixir 1.14+ +* Erlang OTP 24+ * git * file / libmagic * gcc (clang might also work) From 63870c2c170e95bb8377805957cffe8ef020d9b1 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:55:04 -0400 Subject: [PATCH 12/22] Update base image in Dockerfile to newer elixir version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0551a4c9e..c6506c48c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.13.4-erlang-24.3.4.5-alpine-3.15.6 +FROM hexpm/elixir:1.14.3-erlang-25.3-alpine-3.17.2 ENV MIX_ENV=prod ENV ERL_EPMD_ADDRESS=127.0.0.1 From dd44387f1addcad910dae3a148b55e53157004c0 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 17 Mar 2023 15:33:28 +0000 Subject: [PATCH 13/22] Add timeline visibility options --- CHANGELOG.md | 2 + config/config.exs | 5 +- config/description.exs | 6 ++ lib/pleroma/instances/instance.ex | 2 +- .../api_spec/operations/timeline_operation.ex | 3 +- .../controllers/timeline_controller.ex | 46 +++++---- lib/pleroma/web/nodeinfo/nodeinfo.ex | 10 +- .../web/nodeinfo/nodeinfo_controller.ex | 95 +------------------ lib/pleroma/web/router.ex | 2 +- .../controllers/timeline_controller_test.exs | 41 +++++++- .../mastodon_api/views/account_view_test.exs | 4 +- 11 files changed, 90 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b6f775a..1bf6253af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added - Nodeinfo keys for unauthenticated timeline visibility +- Option to disable federated timeline +- Option to make the bubble timeline publicly accessible ## 2023.03 diff --git a/config/config.exs b/config/config.exs index 5eaa8ce76..e216caf9d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -261,7 +261,8 @@ privileged_staff: false, local_bubble: [], max_frontend_settings_json_chars: 100_000, - export_prometheus_metrics: true + export_prometheus_metrics: true, + federated_timeline_available: true config :pleroma, :welcome, direct_message: [ @@ -810,7 +811,7 @@ private_instance? = :if_instance_is_private config :pleroma, :restrict_unauthenticated, - timelines: %{local: private_instance?, federated: private_instance?}, + timelines: %{local: private_instance?, federated: private_instance?, bubble: true}, profiles: %{local: private_instance?, remote: private_instance?}, activities: %{local: private_instance?, remote: private_instance?} diff --git a/config/description.exs b/config/description.exs index 2a2d70a7b..f8496760f 100644 --- a/config/description.exs +++ b/config/description.exs @@ -969,6 +969,12 @@ key: :export_prometheus_metrics, type: :boolean, description: "Enable prometheus metrics (at /api/v1/akkoma/metrics)" + }, + %{ + key: :federated_timeline_available, + type: :boolean, + description: + "Let people view the 'firehose' feed of all public statuses from all instances." } ] }, diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 6ddfa5042..5c70748b6 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -162,7 +162,7 @@ def local do %Instance{ host: Pleroma.Web.Endpoint.host(), favicon: Pleroma.Web.Endpoint.url() <> "/favicon.png", - nodeinfo: Pleroma.Web.Nodeinfo.NodeinfoController.raw_nodeinfo() + nodeinfo: Pleroma.Web.Nodeinfo.Nodeinfo.get_nodeinfo("2.1") } end diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex index 3eb6f700b..45c97cab6 100644 --- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex +++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex @@ -70,7 +70,8 @@ def public_operation do operationId: "TimelineController.public", responses: %{ 200 => Operation.response("Array of Status", "application/json", array_of_statuses()), - 401 => Operation.response("Error", "application/json", ApiError) + 401 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 2d0e36420..c9960187d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.Web.Plugs.RateLimiter plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_public_check when action in [:public, :hashtag]) + plug(:skip_public_check when action in [:public, :hashtag, :bubble]) # TODO: Replace with a macro when there is a Phoenix release with the following commit in it: # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e @@ -28,13 +28,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do plug(RateLimiter, [name: :timeline, bucket_name: :list_timeline] when action == :list) plug(RateLimiter, [name: :timeline, bucket_name: :bubble_timeline] when action == :bubble) - plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct, :bubble]) + plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) plug( OAuthScopesPlug, %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} - when action in [:public, :hashtag] + when action in [:public, :hashtag, :bubble] ) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation @@ -96,21 +96,19 @@ def direct(%{assigns: %{user: user}} = conn, params) do ) end - defp restrict_unauthenticated?(true = _local_only) do - Config.restrict_unauthenticated_access?(:timelines, :local) - end - - defp restrict_unauthenticated?(_) do - Config.restrict_unauthenticated_access?(:timelines, :federated) + defp restrict_unauthenticated?(type) do + Config.restrict_unauthenticated_access?(:timelines, type) end # GET /api/v1/timelines/public def public(%{assigns: %{user: user}} = conn, params) do local_only = params[:local] + timeline_type = if local_only, do: :local, else: :federated - if is_nil(user) and restrict_unauthenticated?(local_only) do - fail_on_bad_auth(conn) - else + with {:enabled, true} <- + {:enabled, local_only || Config.get([:instance, :federated_timeline_available], true)}, + {:authenticated, true} <- + {:authenticated, !(is_nil(user) and restrict_unauthenticated?(timeline_type))} do activities = params |> Map.put(:type, ["Create"]) @@ -131,20 +129,28 @@ def public(%{assigns: %{user: user}} = conn, params) do as: :activity, with_muted: Map.get(params, :with_muted, false) ) + else + {:enabled, false} -> + conn + |> put_status(404) + |> json(%{error: "Federated timeline is disabled"}) + + {:authenticated, false} -> + fail_on_bad_auth(conn) end end # GET /api/v1/timelines/bubble def bubble(%{assigns: %{user: user}} = conn, params) do - bubble_instances = - Enum.uniq( - Config.get([:instance, :local_bubble], []) ++ - [Pleroma.Web.Endpoint.host()] - ) - - if is_nil(user) do + if is_nil(user) and restrict_unauthenticated?(:bubble) do fail_on_bad_auth(conn) else + bubble_instances = + Enum.uniq( + Config.get([:instance, :local_bubble], []) ++ + [Pleroma.Web.Endpoint.host()] + ) + activities = params |> Map.put(:type, ["Create"]) @@ -195,7 +201,7 @@ defp hashtag_fetching(params, user, local_only) do def hashtag(%{assigns: %{user: user}} = conn, params) do local_only = params[:local] - if is_nil(user) and restrict_unauthenticated?(local_only) do + if is_nil(user) and restrict_unauthenticated?(if local_only, do: :local, else: :federated) do fail_on_bad_auth(conn) else activities = hashtag_fetching(params, user, local_only) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index 14e39e6b3..532ae53a7 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -73,9 +73,13 @@ def get_nodeinfo("2.0") do privilegedStaff: Config.get([:instance, :privileged_staff]), localBubbleInstances: Config.get([:instance, :local_bubble], []), publicTimelineVisibility: %{ - federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), - local: !Config.restrict_unauthenticated_access?(:timelines, :local) - } + federated: + !Config.restrict_unauthenticated_access?(:timelines, :federated) && + Config.get([:instance, :federated_timeline_available], true), + local: !Config.restrict_unauthenticated_access?(:timelines, :local), + bubble: !Config.restrict_unauthenticated_access?(:timelines, :bubble) + }, + federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true) } } end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 9a76574d5..4c5a36895 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.Endpoint + alias Pleroma.Web.Nodeinfo.Nodeinfo def schemas(conn, _params) do response = %{ @@ -29,105 +30,15 @@ def schemas(conn, _params) do json(conn, response) end - # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field - # under software. - def raw_nodeinfo do - stats = Stats.get_stats() - - staff_accounts = - User.all_superusers() - |> Enum.map(fn u -> u.ap_id end) - |> Enum.filter(fn u -> not Enum.member?(Config.get([:instance, :staff_transparency]), u) end) - - features = InstanceView.features() - federation = InstanceView.federation() - - %{ - version: "2.0", - software: %{ - name: Pleroma.Application.name() |> String.downcase(), - version: Pleroma.Application.version() - }, - protocols: Publisher.gather_nodeinfo_protocol_names(), - services: %{ - inbound: [], - outbound: [] - }, - openRegistrations: Config.get([:instance, :registrations_open]), - usage: %{ - users: %{ - total: Map.get(stats, :user_count, 0) - }, - localPosts: Map.get(stats, :status_count, 0) - }, - metadata: %{ - nodeName: Config.get([:instance, :name]), - nodeDescription: Config.get([:instance, :description]), - private: !Config.get([:instance, :public], true), - suggestions: %{ - enabled: false - }, - staffAccounts: staff_accounts, - federation: federation, - pollLimits: Config.get([:instance, :poll_limits]), - postFormats: Config.get([:instance, :allowed_post_formats]), - uploadLimits: %{ - 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]) - }, - fieldsLimits: %{ - maxFields: Config.get([:instance, :max_account_fields]), - maxRemoteFields: Config.get([:instance, :max_remote_account_fields]), - nameLength: Config.get([:instance, :account_field_name_length]), - valueLength: Config.get([:instance, :account_field_value_length]) - }, - accountActivationRequired: Config.get([:instance, :account_activation_required], false), - invitesEnabled: Config.get([:instance, :invites_enabled], false), - mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), - features: features, - restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), - skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), - localBubbleInstances: Config.get([:instance, :local_bubble], []), - publicTimelineVisibility: %{ - federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), - local: !Config.restrict_unauthenticated_access?(:timelines, :local) - } - } - } - end - # Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json # and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json - def nodeinfo(conn, %{"version" => "2.0"}) do + def nodeinfo(conn, %{"version" => version}) when version in ["2.0", "2.1"] do conn |> put_resp_header( "content-type", "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" ) - |> json(raw_nodeinfo()) - end - - def nodeinfo(conn, %{"version" => "2.1"}) do - raw_response = raw_nodeinfo() - - updated_software = - raw_response - |> Map.get(:software) - |> Map.put(:repository, Pleroma.Application.repository()) - - response = - raw_response - |> Map.put(:software, updated_software) - |> Map.put(:version, "2.1") - - conn - |> put_resp_header( - "content-type", - "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#; charset=utf-8" - ) - |> json(response) + |> json(Nodeinfo.get_nodeinfo(version)) end def nodeinfo(conn, _) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index faaf3d679..24ca5c37b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -598,7 +598,6 @@ defmodule Pleroma.Web.Router do get("/timelines/home", TimelineController, :home) get("/timelines/direct", TimelineController, :direct) get("/timelines/list/:list_id", TimelineController, :list) - get("/timelines/bubble", TimelineController, :bubble) get("/announcements", AnnouncementController, :index) post("/announcements/:id/dismiss", AnnouncementController, :mark_read) @@ -653,6 +652,7 @@ defmodule Pleroma.Web.Router do get("/timelines/public", TimelineController, :public) get("/timelines/tag/:tag", TimelineController, :hashtag) + get("/timelines/bubble", TimelineController, :bubble) get("/polls/:id", PollController, :show) diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index aa9006681..fcc7a204e 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -408,6 +408,26 @@ test "should not return local-only posts for anonymous users" do assert [] = result end + + test "should return 404 if disabled" do + clear_config([:instance, :federated_timeline_available], false) + + result = + build_conn() + |> get("/api/v1/timelines/public") + |> json_response_and_validate_schema(404) + + assert %{"error" => "Federated timeline is disabled"} = result + end + + test "should not return 404 if local is specified" do + clear_config([:instance, :federated_timeline_available], false) + + result = + build_conn() + |> get("/api/v1/timelines/public?local=true") + |> json_response_and_validate_schema(200) + end end defp local_and_remote_activities do @@ -1036,9 +1056,8 @@ test "with `%{local: true, federated: false}`, forbids unauthenticated access to end describe "bubble" do - setup do: oauth_access(["read:statuses"]) - - test "filtering", %{conn: conn, user: user} do + test "filtering" do + %{conn: conn, user: user} = oauth_access(["read:statuses"]) clear_config([:instance, :local_bubble], []) # our endpoint host has a port in it so let's set the AP ID local_user = insert(:user, %{ap_id: "https://localhost/users/user"}) @@ -1060,7 +1079,7 @@ test "filtering", %{conn: conn, user: user} do assert local_activity.id in one_instance - # If we have others, also include theirs + # If we have others, also include theirs clear_config([:instance, :local_bubble], ["example.com"]) two_instances = @@ -1072,6 +1091,20 @@ test "filtering", %{conn: conn, user: user} do assert local_activity.id in two_instances assert remote_activity.id in two_instances end + + test "restrict_unauthenticated with bubble timeline", %{conn: conn} do + clear_config([:restrict_unauthenticated, :timelines, :bubble], true) + + conn + |> get("/api/v1/timelines/bubble") + |> json_response_and_validate_schema(:unauthorized) + + clear_config([:restrict_unauthenticated, :timelines, :bubble], false) + + conn + |> get("/api/v1/timelines/bubble") + |> json_response_and_validate_schema(200) + end end defp create_remote_activity(user) do diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index c9036d67d..6ef89f799 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -269,8 +269,8 @@ test "Represent a Service(bot) account" do } with_mock( - Pleroma.Web.Nodeinfo.NodeinfoController, - raw_nodeinfo: fn -> %{version: "2.0"} end + Pleroma.Web.Nodeinfo.Nodeinfo, + get_nodeinfo: fn _ -> %{version: "2.0"} end ) do assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true}) From f94e8a3713e5cadb67b53ea05a2bc38eb562c2f5 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 18 Mar 2023 20:49:43 +0000 Subject: [PATCH 14/22] add bubble visibility to description --- config/description.exs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/description.exs b/config/description.exs index f8496760f..6520bc29a 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2999,6 +2999,11 @@ key: :federated, type: :boolean, description: "Disallow viewing the whole known network timeline." + }, + %{ + key: :bubble, + type: :boolean, + description: "Disallow viewing the bubble timeline." } ] }, From 4bbe9c8f5ca0482a75c09335892c788c8de06a45 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 27 Mar 2023 10:03:12 +0100 Subject: [PATCH 15/22] Ship with hehe --- config/custom_emoji.txt | 2 ++ mix.lock | 14 +++++++------- priv/static/emoji/hehe.png | Bin 0 -> 15993 bytes priv/static/emoji/nothehe.png | Bin 0 -> 13628 bytes 4 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 priv/static/emoji/hehe.png create mode 100644 priv/static/emoji/nothehe.png diff --git a/config/custom_emoji.txt b/config/custom_emoji.txt index e69de29bb..7b2e51265 100644 --- a/config/custom_emoji.txt +++ b/config/custom_emoji.txt @@ -0,0 +1,2 @@ +hehe, /emoji/hehe.png, Akkoma +nothehe, /emoji/nothehe.png, Akkoma diff --git a/mix.lock b/mix.lock index 369d1ed25..bee2c1585 100644 --- a/mix.lock +++ b/mix.lock @@ -27,7 +27,7 @@ "earmark": {:hex, :earmark, "1.4.37", "56ce845c543393aa3f9b294c818c3d783452a4a67e4ab18c4303a954a8b59363", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "d86d5e12868db86d5321b00e62a4bbcb4150346e4acc9a90a041fb188a5cb106"}, "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"}, - "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, + "ecto": {:hex, :ecto, "3.9.5", "9f0aa7ae44a1577b651c98791c6988cd1b69b21bc724e3fd67090b97f7604263", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d4f3115d8cbacdc0bfa4b742865459fb1371d0715515842a1fb17fe31920b74c"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"}, "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, @@ -38,7 +38,7 @@ "ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"}, + "ex_doc": {:hex, :ex_doc, "0.29.3", "f07444bcafb302db86e4f02d8bbcd82f2e881a0dcf4f3e4740e4b8128b9353f7", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3dc6787d7b08801ec3b51e9bd26be5e8826fbf1a17e92d1ebc252e1a1c75bfe1"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "excoveralls": {:hex, :excoveralls, "0.15.1", "83c8cf7973dd9d1d853dce37a2fb98aaf29b564bf7d01866e409abf59dac2c0e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8416bd90c0082d56a2178cf46c837595a06575f70a5624f164a1ffe37de07e7"}, @@ -86,14 +86,14 @@ "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.17", "74938b02f3c531bed3f87fe1ea39af6b5b2d26ab1405e77e76b8ef5df9ffa8a1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4b5710e19a29b8dc93b7af4bab4739c067a3cb759af01ffc3057165453dce38"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.4", "615f8f393135de7e0cbb4bd00ba238b1e0cd324b0d90efbaee613c2f02ca5e5c", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "3971221846232021ab5e3c7489fd62ec5bfd6a2e01cae10a317ccf6fb350571c"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, - "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.4", "34c380ef387cc7e8d537854ddd4b7096c79a4397d53587cb80419c782b03fdbc", [:mix], [], "hexpm", "4de415f03faec94d9da9be8c12cb51e9c98cbf66d732b6df669d4562d8e91acc"}, + "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, @@ -117,7 +117,7 @@ "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, - "timex": {:hex, :timex, "3.7.9", "790cdfc4acfce434e442f98c02ea6d84d0239073bfd668968f82ac63e9a6788d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "64691582e5bb87130f721fc709acfb70f24405833998fabf35be968984860ce1"}, + "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, diff --git a/priv/static/emoji/hehe.png b/priv/static/emoji/hehe.png new file mode 100644 index 0000000000000000000000000000000000000000..c02592d97604befbb241ef16c2016d84bc2bb89d GIT binary patch literal 15993 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 Dnw$bX literal 0 HcmV?d00001 diff --git a/priv/static/emoji/nothehe.png b/priv/static/emoji/nothehe.png new file mode 100644 index 0000000000000000000000000000000000000000..427bb6d61f1f4a24e4b89ed48f440a3038c2b594 GIT binary patch literal 13628 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 Date: Wed, 29 Mar 2023 01:59:19 +0100 Subject: [PATCH 16/22] Remove indexer plugin --- config/config.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index e216caf9d..ffb62a28f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -572,8 +572,7 @@ database_prune: 1 ], plugins: [ - Oban.Plugins.Pruner, - {Oban.Plugins.Reindexer, schedule: "@weekly"} + Oban.Plugins.Pruner ], crontab: [ {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, From 3f340cbc43948f3ce0e8bca23142be9cd2454960 Mon Sep 17 00:00:00 2001 From: sadposter Date: Wed, 29 Mar 2023 03:31:56 +0100 Subject: [PATCH 17/22] Only even attempt to fetch local activities by object_id TODO: PLEASE FOR THE LOVE OF KANATAN CACHE THIS --- lib/pleroma/activity.ex | 8 ++++++++ lib/pleroma/web/o_status/o_status_controller.ex | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index c5b514742..0a376be04 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -277,6 +277,14 @@ def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do def get_create_by_object_ap_id_with_object(_), do: nil + def get_local_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do + ap_id + |> create_by_object_ap_id_with_object() + |> where(local: true) + |> Repo.one() + end + + @spec create_by_id_with_object(String.t()) :: t() | nil def create_by_id_with_object(id) do get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"]) diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 7731d847f..79db112df 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -36,7 +36,7 @@ def object(%{assigns: %{format: format}} = conn, _params) def object(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- - {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, + {:activity, Activity.get_local_create_by_object_ap_id_with_object(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, {_, false} <- {:local_public?, Visibility.is_local_public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") From 0151ca1d52d383e4fa7a510a810975fbb09ee6f3 Mon Sep 17 00:00:00 2001 From: sadposter Date: Wed, 29 Mar 2023 03:32:30 +0100 Subject: [PATCH 18/22] Revert "Remove indexer plugin" This reverts commit 1d94f2a424e64082854c9ccf1e2086e314e07852. --- config/config.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index ffb62a28f..e216caf9d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -572,7 +572,8 @@ database_prune: 1 ], plugins: [ - Oban.Plugins.Pruner + Oban.Plugins.Pruner, + {Oban.Plugins.Reindexer, schedule: "@weekly"} ], crontab: [ {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, From d85d1e128a0ca836e8976202781902383aea3d89 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 29 Mar 2023 11:44:03 +0100 Subject: [PATCH 19/22] we don't actually need the object on redirect --- lib/pleroma/activity.ex | 4 ++-- lib/pleroma/web/o_status/o_status_controller.ex | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 0a376be04..925612d3c 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -277,9 +277,9 @@ def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do def get_create_by_object_ap_id_with_object(_), do: nil - def get_local_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do + def get_local_create_by_object_ap_id(ap_id) when is_binary(ap_id) do ap_id - |> create_by_object_ap_id_with_object() + |> create_by_object_ap_id() |> where(local: true) |> Repo.one() end diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 79db112df..95a22895e 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -36,7 +36,7 @@ def object(%{assigns: %{format: format}} = conn, _params) def object(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- - {:activity, Activity.get_local_create_by_object_ap_id_with_object(id)}, + {:activity, Activity.get_local_create_by_object_ap_id(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, {_, false} <- {:local_public?, Visibility.is_local_public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") From 66d162bb9edf79455558bdf7e267c08f8c3cf964 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 29 Mar 2023 12:01:16 +0100 Subject: [PATCH 20/22] Add debug logs to timeline rendering to assist debugging --- lib/pleroma/activity.ex | 1 - lib/pleroma/user.ex | 2 +- .../controllers/timeline_controller.ex | 27 +++++++++++++++++++ .../web/mastodon_api/views/status_view.ex | 13 +++++++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 925612d3c..c94667fb9 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -284,7 +284,6 @@ def get_local_create_by_object_ap_id(ap_id) when is_binary(ap_id) do |> Repo.one() end - @spec create_by_id_with_object(String.t()) :: t() | nil def create_by_id_with_object(id) do get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"]) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 480521984..ead37ccca 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -374,7 +374,7 @@ def banner_url(user, options \\ []) do do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options) end - defp do_optional_url(field, default, options \\ []) do + defp do_optional_url(field, default, options) do case field do %{"url" => [%{"href" => href} | _]} when is_binary(href) -> href diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index c9960187d..1d4e734a4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -37,10 +37,16 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do when action in [:public, :hashtag, :bubble] ) + require Logger + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation # GET /api/v1/timelines/home def home(%{assigns: %{user: user}} = conn, params) do + %{nickname: nickname} = user + + Logger.debug("TimelineController.home: #{nickname}") + followed_hashtags = user |> User.followed_hashtags() @@ -58,11 +64,15 @@ def home(%{assigns: %{user: user}} = conn, params) do |> Map.put(:followed_hashtags, followed_hashtags) |> Map.delete(:local) + Logger.debug("TimelineController.home: #{nickname} - fetching activities") + activities = [user.ap_id | User.following(user)] |> ActivityPub.fetch_activities(params) |> Enum.reverse() + Logger.debug("TimelineController.home: #{nickname} - rendering") + conn |> add_link_headers(activities) |> render("index.json", @@ -75,6 +85,8 @@ def home(%{assigns: %{user: user}} = conn, params) do # GET /api/v1/timelines/direct def direct(%{assigns: %{user: user}} = conn, params) do + Logger.debug("TimelineController.direct: #{user.nickname}") + params = params |> Map.put(:type, "Create") @@ -82,11 +94,15 @@ def direct(%{assigns: %{user: user}} = conn, params) do |> Map.put(:user, user) |> Map.put(:visibility, "direct") + Logger.debug("TimelineController.direct: #{user.nickname} - fetching activities") + activities = [user.ap_id] |> ActivityPub.fetch_activities_query(params) |> Pagination.fetch_paginated(params) + Logger.debug("TimelineController.direct: #{user.nickname} - rendering") + conn |> add_link_headers(activities) |> render("index.json", @@ -102,6 +118,7 @@ defp restrict_unauthenticated?(type) do # GET /api/v1/timelines/public def public(%{assigns: %{user: user}} = conn, params) do + Logger.debug("TimelineController.public") local_only = params[:local] timeline_type = if local_only, do: :local, else: :federated @@ -109,6 +126,8 @@ def public(%{assigns: %{user: user}} = conn, params) do {:enabled, local_only || Config.get([:instance, :federated_timeline_available], true)}, {:authenticated, true} <- {:authenticated, !(is_nil(user) and restrict_unauthenticated?(timeline_type))} do + Logger.debug("TimelineController.public: fetching activities") + activities = params |> Map.put(:type, ["Create"]) @@ -121,6 +140,8 @@ def public(%{assigns: %{user: user}} = conn, params) do |> Map.put(:includes_local_public, not is_nil(user)) |> ActivityPub.fetch_public_activities() + Logger.debug("TimelineController.public: rendering") + conn |> add_link_headers(activities, %{"local" => local_only}) |> render("index.json", @@ -142,6 +163,8 @@ def public(%{assigns: %{user: user}} = conn, params) do # GET /api/v1/timelines/bubble def bubble(%{assigns: %{user: user}} = conn, params) do + Logger.debug("TimelineController.bubble") + if is_nil(user) and restrict_unauthenticated?(:bubble) do fail_on_bad_auth(conn) else @@ -151,6 +174,8 @@ def bubble(%{assigns: %{user: user}} = conn, params) do [Pleroma.Web.Endpoint.host()] ) + Logger.debug("TimelineController.bubble: fetching activities") + activities = params |> Map.put(:type, ["Create"]) @@ -160,6 +185,8 @@ def bubble(%{assigns: %{user: user}} = conn, params) do |> Map.put(:instance, bubble_instances) |> ActivityPub.fetch_public_activities() + Logger.debug("TimelineController.bubble: rendering") + conn |> add_link_headers(activities) |> render("index.json", diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3868da8d9..47d1616c4 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -21,6 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy alias Pleroma.Web.PleromaAPI.EmojiReactionController + require Logger import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2] @@ -87,6 +88,7 @@ defp reblogged?(activity, %User{ap_id: ap_id}) do defp reblogged?(_activity, _user), do: false def render("index.json", opts) do + Logger.debug("Rendering index") reading_user = opts[:for] # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list activities = Enum.filter(opts.activities, & &1) @@ -136,8 +138,10 @@ def render("index.json", opts) do def render( "show.json", - %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts + %{activity: %{id: id, data: %{"type" => "Announce", "object" => _object}} = activity} = + opts ) do + Logger.debug("Rendering reblog #{id}") user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) object = Object.normalize(activity, fetch: false) @@ -209,7 +213,9 @@ def render( } end - def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = activity} = opts) do + Logger.debug("Rendering status #{id}") + with %Object{} = object <- Object.normalize(activity, fetch: false) do user = CommonAPI.get_user(activity.data["actor"]) user_follower_address = user.follower_address @@ -428,6 +434,7 @@ def render("show.json", _) do end def render("history.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + Logger.debug("Rendering history for #{activity.id}") object = Object.normalize(activity, fetch: false) hashtags = Object.hashtags(object) @@ -614,6 +621,8 @@ def render("attachment_meta.json", %{ def render("attachment_meta.json", _), do: nil def render("context.json", %{activity: activity, activities: activities, user: user}) do + Logger.debug("Rendering context for #{activity.id}") + %{ancestors: ancestors, descendants: descendants} = activities |> Enum.reverse() From 2a8c1f41924e3f7092ffea0fb407c2906f705242 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 29 Mar 2023 14:11:00 +0100 Subject: [PATCH 21/22] Add extra diagnostic tasks in --- lib/mix/tasks/pleroma/diagnostics.ex | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/mix/tasks/pleroma/diagnostics.ex b/lib/mix/tasks/pleroma/diagnostics.ex index 3914540ca..3b6063723 100644 --- a/lib/mix/tasks/pleroma/diagnostics.ex +++ b/lib/mix/tasks/pleroma/diagnostics.ex @@ -82,4 +82,46 @@ def run(["user_timeline", nickname, reading_nickname]) do Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity) |> IO.puts() end + + def run(["notifications", nickname]) do + start_pleroma() + user = Repo.get_by!(User, nickname: nickname) + account_ap_id = user.ap_id + options = %{account_ap_id: user.ap_id} + + query = + user + |> Pleroma.Notification.for_user_query(options) + |> where([n, a], a.actor == ^account_ap_id) + |> limit(20) + + Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity) + |> IO.puts() + end + + def run(["known_network", nickname]) do + start_pleroma() + user = Repo.get_by!(User, nickname: nickname) + + params = + %{} + |> Map.put(:type, ["Create"]) + |> Map.put(:local_only, false) + |> Map.put(:blocking_user, user) + |> Map.put(:muting_user, user) + |> Map.put(:reply_filtering_user, user) + # Restricts unfederated content to authenticated users + |> Map.put(:includes_local_public, not is_nil(user)) + |> Map.put(:restrict_unlisted, true) + + query = + Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query( + [Pleroma.Constants.as_public()], + params + ) + |> limit(20) + + Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity) + |> IO.puts() + end end From d8d9edee98ef4cf89fedf8028236b496b699e080 Mon Sep 17 00:00:00 2001 From: ilja Date: Mon, 3 Apr 2023 11:18:28 +0200 Subject: [PATCH 22/22] Add YunoHost to installation guides --- README.md | 3 +++ docs/docs/installation/yunohost_en.md | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 docs/docs/installation/yunohost_en.md diff --git a/README.md b/README.md index 8d35212aa..e4aa25715 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ If your platform is not supported, or you just want to be able to edit the sourc ### Docker Docker installation is supported via [this setup](https://docs.akkoma.dev/stable/installation/docker_en/) +### Packages +Akkoma is packaged for [YunoHost](https://yunohost.org) and can be found and installed from the [YunoHost app catalogue](https://yunohost.org/#/apps). + ### Compilation Troubleshooting If you ever encounter compilation issues during the updating of Akkoma, you can try these commands and see if they fix things: diff --git a/docs/docs/installation/yunohost_en.md b/docs/docs/installation/yunohost_en.md new file mode 100644 index 000000000..0d3adb4fe --- /dev/null +++ b/docs/docs/installation/yunohost_en.md @@ -0,0 +1,9 @@ +# Installing on Yunohost + +[YunoHost](https://yunohost.org) is a server operating system aimed at self-hosting. The YunoHost community maintains a package of Akkoma which allows you to install Akkoma on YunoHost. You can install it via the normal way through the admin web interface, or through the CLI. More information can be found at [the repo of the package](https://github.com/YunoHost-Apps/akkoma_ynh). + +## Questions + +Questions and problems related to the YunoHost parts can be done through the [YunoHost channels](https://yunohost.org/en/help). + +For questions about Akkoma, check out the [Akkoma community channels](../../#community-channels).