From bd52e2aec7f07da3bc3609f72f7f1bf5969c9baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Feb 2022 18:03:53 +0100 Subject: Instance rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 56 ++++++++++ .../web/admin_api/controllers/rule_controller.ex | 64 ++++++++++++ lib/pleroma/web/admin_api/views/rule_view.ex | 21 ++++ .../api_spec/operations/admin/rule_operation.ex | 113 +++++++++++++++++++++ .../web/mastodon_api/views/instance_view.ex | 7 ++ lib/pleroma/web/router.ex | 5 + 6 files changed, 266 insertions(+) create mode 100644 lib/pleroma/rule.ex create mode 100644 lib/pleroma/web/admin_api/controllers/rule_controller.ex create mode 100644 lib/pleroma/web/admin_api/views/rule_view.ex create mode 100644 lib/pleroma/web/api_spec/operations/admin/rule_operation.ex (limited to 'lib') diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex new file mode 100644 index 000000000..d772a32bd --- /dev/null +++ b/lib/pleroma/rule.ex @@ -0,0 +1,56 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Rule do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Repo + alias Pleroma.Rule + + schema "rules" do + field(:priority, :integer, default: 0) + field(:text, :string) + + timestamps() + end + + def changeset(%Rule{} = rule, params \\ %{}) do + rule + |> cast(params, [:priority, :text]) + |> validate_required([:text]) + end + + def query do + Rule + |> order_by(asc: :priority) + end + + def get(id), do: Repo.get(__MODULE__, id) + + def create(params) do + {:ok, rule} = + %Rule{} + |> changeset(params) + |> Repo.insert() + + rule + end + + def update(params, id) do + {:ok, rule} = + get(id) + |> changeset(params) + |> Repo.update() + + rule + end + + def delete(id) do + get(id) + |> Repo.delete() + end +end diff --git a/lib/pleroma/web/admin_api/controllers/rule_controller.ex b/lib/pleroma/web/admin_api/controllers/rule_controller.ex new file mode 100644 index 000000000..2db88b6ba --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/rule_controller.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleController do + use Pleroma.Web, :controller + + alias Pleroma.Repo + alias Pleroma.Rule + alias Pleroma.Web.Plugs.OAuthScopesPlug + + import Pleroma.Web.ControllerHelper, + only: [ + json_response: 3 + ] + + require Logger + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug( + OAuthScopesPlug, + %{scopes: ["admin:write"]} + when action in [:create, :update, :delete] + ) + + plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :index) + + action_fallback(AdminAPI.FallbackController) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.RuleOperation + + def index(conn, _) do + rules = + Rule.query() + |> Repo.all() + + render(conn, "index.json", rules: rules) + end + + def create(%{body_params: params} = conn, _) do + rule = + params + |> Rule.create() + + render(conn, "show.json", rule: rule) + end + + def update(%{body_params: params} = conn, %{id: id}) do + rule = + params + |> Rule.update(id) + + render(conn, "show.json", rule: rule) + end + + def delete(conn, %{id: id}) do + with {:ok, _} <- Rule.delete(id) do + json(conn, %{}) + else + _ -> json_response(conn, :bad_request, "") + end + end +end diff --git a/lib/pleroma/web/admin_api/views/rule_view.ex b/lib/pleroma/web/admin_api/views/rule_view.ex new file mode 100644 index 000000000..f29145248 --- /dev/null +++ b/lib/pleroma/web/admin_api/views/rule_view.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleView do + use Pleroma.Web, :view + + require Pleroma.Constants + + def render("index.json", %{rules: rules} = _opts) do + render_many(rules, __MODULE__, "show.json") + end + + def render("show.json", %{rule: rule} = _opts) do + %{ + id: rule.id, + priority: rule.priority, + text: rule.text + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex new file mode 100644 index 000000000..28f2be5e7 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -0,0 +1,113 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Retrieve a list of instance rules", + operationId: "AdminAPI.RuleController.index", + security: [%{"oAuth" => ["admin:read"]}], + responses: %{ + 200 => + Operation.response("Response", "application/json", %Schema{ + type: :array, + items: rule() + }), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + def create_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Create new rule", + operationId: "AdminAPI.RuleController.create", + security: [%{"oAuth" => ["admin:write"]}], + parameters: admin_api_params(), + requestBody: request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Response", "application/json", rule()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Modify existing rule", + operationId: "AdminAPI.RuleController.update", + security: [%{"oAuth" => ["admin:write"]}], + parameters: [Operation.parameter(:id, :path, :string, "Rule ID")], + requestBody: request_body("Parameters", update_request(), required: true), + responses: %{ + 200 => Operation.response("Response", "application/json", rule()), + 400 => Operation.response("Bad Request", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Instance rule managment"], + summary: "Delete rule", + operationId: "AdminAPI.RuleController.delete", + parameters: [Operation.parameter(:id, :path, :string, "Rule ID")], + security: [%{"oAuth" => ["admin:write"]}], + responses: %{ + 200 => empty_object_response(), + 404 => Operation.response("Not Found", "application/json", ApiError), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + type: :object, + required: [:text], + properties: %{ + priority: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } + end + + defp update_request do + %Schema{ + type: :object, + properties: %{ + priority: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } + end + + defp rule do + %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + priority: %Schema{type: :integer}, + text: %Schema{type: :string}, + created_at: %Schema{type: :string, format: :"date-time"} + } + } + end +end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index ee52475d5..9652da53a 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -40,6 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), + rules: rules(), pleroma: %{ metadata: %{ account_activation_required: Keyword.get(instance, :account_activation_required), @@ -137,4 +138,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end + + def rules do + Pleroma.Rule.query() + |> Pleroma.Repo.all() + |> Enum.map(&%{id: &1.id, text: &1.text}) + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ceb6c3cfd..e8b7b17aa 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -229,6 +229,11 @@ defmodule Pleroma.Web.Router do post("/frontends/install", FrontendController, :install) post("/backups", AdminAPIController, :create_backup) + + get("/rules", RuleController, :index) + post("/rules", RuleController, :create) + patch("/rules/:id", RuleController, :update) + delete("/rules/:id", RuleController, :delete) end # AdminAPI: admins and mods (staff) can perform these actions (if enabled by config) -- cgit v1.2.3 From 432599311d3aab8829ed2cc7f795a1662e7a8f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Feb 2022 19:13:30 +0100 Subject: Add GET /api/v1/instance/rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../api_spec/operations/admin/rule_operation.ex | 2 +- .../web/api_spec/operations/instance_operation.ex | 27 +++++++++++++++++++++- .../controllers/instance_controller.ex | 5 ++++ .../web/mastodon_api/views/instance_view.ex | 4 ++++ lib/pleroma/web/router.ex | 1 + 5 files changed, 37 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex index 28f2be5e7..ed0d9eaf6 100644 --- a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do def index_operation do %Operation{ tags: ["Instance rule managment"], - summary: "Retrieve a list of instance rules", + summary: "Retrieve list of instance rules", operationId: "AdminAPI.RuleController.index", security: [%{"oAuth" => ["admin:read"]}], responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 3c4b504fe..e66e5b7a3 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -34,6 +34,17 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do } end + def rules_operation do + %Operation{ + tags: ["Instance"], + summary: "Retrieve list of instance rules", + operationId: "InstanceController.rules", + responses: %{ + 200 => Operation.response("Array of domains", "application/json", array_of_rules()) + } + } + end + defp instance do %Schema{ type: :object, @@ -160,7 +171,8 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do "urls" => %{ "streaming_api" => "wss://lain.com" }, - "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)" + "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)", + "rules" => array_of_rules() } } end @@ -172,4 +184,17 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do example: ["pleroma.site", "lain.com", "bikeshed.party"] } end + + defp array_of_rules do + %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } + } + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 6410e872c..d6aa89432 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -20,4 +20,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do def peers(conn, _params) do json(conn, Pleroma.Stats.get_peers()) end + + @doc "GET /api/v1/instance/rules" + def rules(conn, _params) do + render(conn, "rules.json") + end end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 9652da53a..feb94b40b 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -58,6 +58,10 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + def render("rules.json", _) do + rules() + end + def features do [ "pleroma_api", diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e8b7b17aa..1246989b0 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -606,6 +606,7 @@ defmodule Pleroma.Web.Router do get("/instance", InstanceController, :show) get("/instance/peers", InstanceController, :peers) + get("/instance/rules", InstanceController, :rules) get("/statuses", StatusController, :index) get("/statuses/:id", StatusController, :show) -- cgit v1.2.3 From 384f8bfa786f51f3abec101e2ab78917f324a4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 7 Feb 2022 23:41:41 +0100 Subject: Instance rules: Use render_many MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../web/admin_api/controllers/rule_controller.ex | 2 -- lib/pleroma/web/mastodon_api/views/instance_view.ex | 19 +++++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/rule_controller.ex b/lib/pleroma/web/admin_api/controllers/rule_controller.ex index 2db88b6ba..43b2f209a 100644 --- a/lib/pleroma/web/admin_api/controllers/rule_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/rule_controller.ex @@ -14,8 +14,6 @@ defmodule Pleroma.Web.AdminAPI.RuleController do json_response: 3 ] - require Logger - plug(Pleroma.Web.ApiSpec.CastAndValidate) plug( diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index feb94b40b..8379731e4 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -40,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do background_image: Pleroma.Web.Endpoint.url() <> Keyword.get(instance, :background_image), shout_limit: Config.get([:shout, :limit]), description_limit: Keyword.get(instance, :description_limit), - rules: rules(), + rules: render(__MODULE__, "rules.json"), pleroma: %{ metadata: %{ account_activation_required: Keyword.get(instance, :account_activation_required), @@ -59,7 +59,16 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do end def render("rules.json", _) do - rules() + Pleroma.Rule.query() + |> Pleroma.Repo.all() + |> render_many(__MODULE__, "rule.json", as: :rule) + end + + def render("rule.json", %{rule: rule}) do + %{ + id: rule.id, + text: rule.text + } end def features do @@ -142,10 +151,4 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end - - def rules do - Pleroma.Rule.query() - |> Pleroma.Repo.all() - |> Enum.map(&%{id: &1.id, text: &1.text}) - end end -- cgit v1.2.3 From 574db5b988e3caca14d1729a729af83d2f23e214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 20 Feb 2022 21:44:42 +0100 Subject: Allow submitting an array of rule_ids to /api/v1/reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/constants.ex | 3 ++- lib/pleroma/rule.ex | 5 +++++ lib/pleroma/web/activity_pub/utils.ex | 8 ++++++-- lib/pleroma/web/admin_api/views/report_view.ex | 15 ++++++++++++++- .../web/api_spec/operations/admin/report_operation.ex | 10 ++++++++++ lib/pleroma/web/api_spec/operations/report_operation.ex | 9 ++++++++- lib/pleroma/web/common_api.ex | 17 +++++++++++++++-- 7 files changed, 60 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index a42c71d23..bf43becb3 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Constants do "context_id", "deleted_activity_id", "pleroma_internal", - "generator" + "generator", + "rules" ] ) diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index d772a32bd..486cff8cc 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -29,6 +29,11 @@ defmodule Pleroma.Rule do |> order_by(asc: :priority) end + def get(ids) when is_list(ids) do + from(r in __MODULE__, where: r.id in ^ids) + |> Repo.all() + end + def get(id), do: Repo.get(__MODULE__, id) def create(params) do diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 9cde7805c..72d17e2aa 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -692,14 +692,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Flag-related helpers @spec make_flag_data(map(), map()) :: map() - def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do + def make_flag_data( + %{actor: actor, context: context, content: content} = params, + additional + ) do %{ "type" => "Flag", "actor" => actor.ap_id, "content" => content, "object" => build_flag_object(params), "context" => context, - "state" => "open" + "state" => "open", + "rules" => Map.get(params, :rules, nil) } |> Map.merge(additional) end diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index b761dbb22..ca70f4359 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -6,10 +6,12 @@ defmodule Pleroma.Web.AdminAPI.ReportView do use Pleroma.Web, :view alias Pleroma.HTML + alias Pleroma.Rule alias Pleroma.User alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.MastodonAPI.StatusView defdelegate merge_account_views(user), to: AdminAPI.AccountView @@ -46,7 +48,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do as: :activity }), state: report.data["state"], - notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) + notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}), + rules: rules(Map.get(report.data, "rules", nil)) } end @@ -71,4 +74,14 @@ defmodule Pleroma.Web.AdminAPI.ReportView do created_at: Utils.to_masto_date(inserted_at) } end + + defp rules(nil) do + [] + end + + defp rules(rule_ids) do + rule_ids + |> Rule.get() + |> render_many(InstanceView, "rule.json", as: :rule) + end end diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index 312e091a5..bb71abbd1 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -169,6 +169,16 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do inserted_at: %Schema{type: :string, format: :"date-time"} } } + }, + rules: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } } } } diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex index c74ac7d5f..fd68f67a2 100644 --- a/lib/pleroma/web/api_spec/operations/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -53,6 +53,12 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do default: false, description: "If the account is remote, should the report be forwarded to the remote admin?" + }, + rule_ids: %Schema{ + type: :array, + nullable: true, + items: %Schema{type: :number}, + description: "Array of rules" } }, required: [:account_id], @@ -60,7 +66,8 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do "account_id" => "123", "status_ids" => ["1337"], "comment" => "bad status!", - "forward" => "false" + "forward" => "false", + "rule_ids" => [3] } } end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 1b95ee89c..9f8d4def4 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object + alias Pleroma.Rule alias Pleroma.ThreadMute alias Pleroma.User alias Pleroma.UserRelationship @@ -505,14 +506,16 @@ defmodule Pleroma.Web.CommonAPI do def report(user, data) do with {:ok, account} <- get_reported_account(data.account_id), {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]), - {:ok, statuses} <- get_report_statuses(account, data) do + {:ok, statuses} <- get_report_statuses(account, data), + rules <- get_report_rules(Map.get(data, :rule_ids, nil)) do ActivityPub.flag(%{ context: Utils.generate_context_id(), actor: user, account: account, statuses: statuses, content: content_html, - forward: Map.get(data, :forward, false) + forward: Map.get(data, :forward, false), + rules: rules }) end end @@ -524,6 +527,16 @@ defmodule Pleroma.Web.CommonAPI do end end + defp get_report_rules(nil) do + nil + end + + defp get_report_rules(rule_ids) do + rule_ids + |> Rule.get() + |> Enum.map(& &1.id) + end + def update_report_state(activity_ids, state) when is_list(activity_ids) do case Utils.update_report_state(activity_ids, state) do :ok -> {:ok, activity_ids} -- cgit v1.2.3 From 5c383ada8ad7491125a8264a8a03d85fe822f61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Mar 2022 21:45:34 +0100 Subject: Correctly order rules by id/creation date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index 486cff8cc..b1db1dc0c 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Rule do def query do Rule |> order_by(asc: :priority) + |> order_by(asc: :id) end def get(ids) when is_list(ids) do -- cgit v1.2.3 From b354d70e85bbf0f685f3d56f7377fde2efce4187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 30 May 2022 12:30:03 +0200 Subject: Apply, suggestions, use strings for actual Mastodon API compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 2 ++ lib/pleroma/web/admin_api/views/report_view.ex | 10 ++++++---- lib/pleroma/web/admin_api/views/rule_view.ex | 2 +- lib/pleroma/web/api_spec/operations/admin/report_operation.ex | 2 +- lib/pleroma/web/api_spec/operations/admin/rule_operation.ex | 5 ++--- lib/pleroma/web/api_spec/operations/instance_operation.ex | 2 +- lib/pleroma/web/api_spec/operations/report_operation.ex | 4 ++-- lib/pleroma/web/common_api.ex | 3 +-- lib/pleroma/web/mastodon_api/views/instance_view.ex | 2 +- 9 files changed, 17 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index b1db1dc0c..611e945b3 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -37,6 +37,8 @@ defmodule Pleroma.Rule do def get(id), do: Repo.get(__MODULE__, id) + def exists?(id), do: not is_nil(get(id)) + def create(params) do {:ok, rule} = %Rule{} diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index ca70f4359..b4b0be267 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do alias Pleroma.User alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.Report + alias Pleroma.Web.AdminAPI.RuleView alias Pleroma.Web.CommonAPI.Utils - alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.MastodonAPI.StatusView defdelegate merge_account_views(user), to: AdminAPI.AccountView @@ -80,8 +80,10 @@ defmodule Pleroma.Web.AdminAPI.ReportView do end defp rules(rule_ids) do - rule_ids - |> Rule.get() - |> render_many(InstanceView, "rule.json", as: :rule) + rules = + rule_ids + |> Rule.get() + + render(RuleView, "index.json", rules: rules) end end diff --git a/lib/pleroma/web/admin_api/views/rule_view.ex b/lib/pleroma/web/admin_api/views/rule_view.ex index f29145248..abfdd593f 100644 --- a/lib/pleroma/web/admin_api/views/rule_view.ex +++ b/lib/pleroma/web/admin_api/views/rule_view.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.AdminAPI.RuleView do def render("show.json", %{rule: rule} = _opts) do %{ - id: rule.id, + id: to_string(rule.id), priority: rule.priority, text: rule.text } diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index bb71abbd1..b90bbd592 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -175,7 +175,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do items: %Schema{ type: :object, properties: %{ - id: %Schema{type: :integer}, + id: %Schema{type: :string}, text: %Schema{type: :string} } } diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex index ed0d9eaf6..2360880e4 100644 --- a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -103,10 +103,9 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do %Schema{ type: :object, properties: %{ - id: %Schema{type: :integer}, + id: %Schema{type: :string}, priority: %Schema{type: :integer}, - text: %Schema{type: :string}, - created_at: %Schema{type: :string, format: :"date-time"} + text: %Schema{type: :string} } } end diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index e66e5b7a3..f3dba108e 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -191,7 +191,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do items: %Schema{ type: :object, properties: %{ - id: %Schema{type: :integer}, + id: %Schema{type: :string}, text: %Schema{type: :string} } } diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex index fd68f67a2..f5f88974c 100644 --- a/lib/pleroma/web/api_spec/operations/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -57,7 +57,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do rule_ids: %Schema{ type: :array, nullable: true, - items: %Schema{type: :number}, + items: %Schema{type: :string}, description: "Array of rules" } }, @@ -67,7 +67,7 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do "status_ids" => ["1337"], "comment" => "bad status!", "forward" => "false", - "rule_ids" => [3] + "rule_ids" => ["3"] } } end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 9f8d4def4..6fd744ddc 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -533,8 +533,7 @@ defmodule Pleroma.Web.CommonAPI do defp get_report_rules(rule_ids) do rule_ids - |> Rule.get() - |> Enum.map(& &1.id) + |> Enum.filter(&Rule.exists?/1) end def update_report_state(activity_ids, state) when is_list(activity_ids) do diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 8379731e4..c7f5ff554 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -66,7 +66,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do def render("rule.json", %{rule: rule}) do %{ - id: rule.id, + id: to_string(rule.id), text: rule.text } end -- cgit v1.2.3 From 0ecd6ba35e868eac296b013f743d82a120dd68db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 30 May 2022 12:50:44 +0200 Subject: AdminAPI: Allow filtering reports by rule_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/activity_pub/activity_pub.ex | 10 ++++++++++ lib/pleroma/web/api_spec/operations/admin/report_operation.ex | 6 ++++++ 2 files changed, 16 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 064f93b22..e54adf611 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1191,6 +1191,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_filtered(query, _), do: query + defp restrict_rule(query, %{rule_id: rule_id}) do + from( + activity in query, + where: fragment("(?)->'rules' \\? (?)", activity.data, ^rule_id) + ) + end + + defp restrict_rule(query, _), do: query + defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query defp exclude_poll_votes(query, _) do @@ -1353,6 +1362,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_instance(opts) |> restrict_announce_object_actor(opts) |> restrict_filtered(opts) + |> restrict_rule(opts) |> Activity.restrict_deactivated_users() |> exclude_poll_votes(opts) |> exclude_chat_messages(opts) diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index b90bbd592..18386296f 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -30,6 +30,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do report_state(), "Filter by report state" ), + Operation.parameter( + :rule_id, + :query, + %Schema{type: :string}, + "Filter by selected rule id" + ), Operation.parameter( :limit, :query, -- cgit v1.2.3 From 5846e7d5f6b91ab63270f2104543d874589d39ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 1 Jun 2022 16:03:22 +0200 Subject: Use Repo.exists? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index 611e945b3..c8e3470c7 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -37,7 +37,10 @@ defmodule Pleroma.Rule do def get(id), do: Repo.get(__MODULE__, id) - def exists?(id), do: not is_nil(get(id)) + def exists?(id) do + from(r in __MODULE__, where: r.id == ^id) + |> Repo.exists?() + end def create(params) do {:ok, rule} = -- cgit v1.2.3 From 6b74a5527410c5e98a6c4906a8e6f9c4d9f5e5d1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 1 Jul 2022 15:40:40 -0500 Subject: InstanceStatic should have reasonable caching While here fix the naming convention of the old module attribute restricting caching and add a new one for the default cache value All frontends should be shipped with versioned assets. There be dragons if you don't. --- lib/pleroma/web/endpoint.ex | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index d8d40cceb..17bd956d8 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Web.Plugs.HTTPSecurityPlug) plug(Pleroma.Web.Plugs.UploadedMedia) - @static_cache_control "public, no-cache" + @static_cache_control "public, max-age=1209600" + @static_cache_disabled "public, no-cache" # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well @@ -30,22 +31,32 @@ defmodule Pleroma.Web.Endpoint do from: :pleroma, only: ["emoji", "images"], gzip: true, - cache_control_for_etags: "public, max-age=1209600", + cache_control_for_etags: @static_cache_control, headers: %{ - "cache-control" => "public, max-age=1209600" + "cache-control" => @static_cache_control } ) plug(Pleroma.Web.Plugs.InstanceStatic, at: "/", gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled + } + ) + + plug(Pleroma.Web.Plugs.FrontendStatic, + at: "/", + frontend_type: :primary, + only: ["index.html"], + gzip: true, + cache_control_for_etags: @static_cache_disabled, + headers: %{ + "cache-control" => @static_cache_disabled } ) - # Careful! No `only` restriction here, as we don't know what frontends contain. plug(Pleroma.Web.Plugs.FrontendStatic, at: "/", frontend_type: :primary, @@ -62,9 +73,9 @@ defmodule Pleroma.Web.Endpoint do at: "/pleroma/admin", frontend_type: :admin, gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) @@ -79,9 +90,9 @@ defmodule Pleroma.Web.Endpoint do only: Pleroma.Constants.static_only_files(), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) -- cgit v1.2.3 From 6b8c5e12dfe759ac1286e81e72ad7f8727e01386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 26 Oct 2023 23:30:38 +0200 Subject: Add contact account to InstanceView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 1b01d7371..06df63b0f 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -30,6 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do languages: Keyword.get(instance, :languages, ["en"]), registrations: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), + contact_account: contact_account(Keyword.get(instance, :contact_username)), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), max_media_attachments: Keyword.get(instance, :max_media_attachments), @@ -141,4 +142,20 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do value_length: Config.get([:instance, :account_field_value_length]) } end + + defp contact_account(nil), do: nil + + defp contact_account("@" <> username) do + contact_account(username) + end + + defp contact_account(username) do + user = Pleroma.User.get_cached_by_nickname(username) + + if user do + Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user, for: nil}) + else + nil + end + end end -- cgit v1.2.3 From 93370b870a8d16b5dada5ef7c7e3ef2de378395e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 7 Aug 2022 23:56:52 +0200 Subject: Expose nonAnonymous field from Smithereen polls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../web/activity_pub/object_validators/question_validator.ex | 1 + lib/pleroma/web/api_spec/schemas/poll.ex | 11 ++++++++++- lib/pleroma/web/mastodon_api/views/poll_view.ex | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 621085e6c..7f9d4d648 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do field(:closed, ObjectValidators.DateTime) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) + field(:nonAnonymous, :boolean) embeds_many(:anyOf, QuestionOptionsValidator) embeds_many(:oneOf, QuestionOptionsValidator) end diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 91570582b..cb2ffdc68 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -56,6 +56,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do } }, description: "Possible answers for the poll." + }, + pleroma: %Schema{ + type: :object, + properties: %{ + non_anonymous: %Schema{type: :boolean, description: "Is the voters collection public?"} + } } }, example: %{ @@ -79,7 +85,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do votes_count: 4 } ], - emojis: [] + emojis: [], + pleroma: %{ + non_anonymous: false + } } }) end diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 34e23873e..1e3c9f36d 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -21,7 +21,10 @@ defmodule Pleroma.Web.MastodonAPI.PollView do votes_count: votes_count, voters_count: voters_count(object), options: options, - emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) + emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]), + pleroma: %{ + non_anonymous: object.data["nonAnonymous"] || false + } } if params[:for] do -- cgit v1.2.3 From 50e7706b269d6008ae4778db86bc4462ffce5ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 20 Nov 2022 23:19:52 +0100 Subject: Verify link ownership with rel="me" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/user.ex | 63 +++++++++++++++++++++++++++++++- lib/pleroma/workers/background_worker.ex | 5 +++ 2 files changed, 66 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ce125d608..d81aa5252 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -8,6 +8,7 @@ defmodule Pleroma.User do import Ecto.Changeset import Ecto.Query import Ecto, only: [assoc: 2] + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] alias Ecto.Multi alias Pleroma.Activity @@ -595,9 +596,23 @@ defmodule Pleroma.User do defp put_fields(changeset) do if raw_fields = get_change(changeset, :raw_fields) do + old_fields = changeset.data.raw_fields + raw_fields = raw_fields |> Enum.filter(fn %{"name" => n} -> n != "" end) + |> Enum.map(fn field -> + previous = + old_fields + |> Enum.find(fn %{"value" => value} -> field["value"] == value end) + + if previous && Map.has_key?(previous, "verified_at") do + field + |> Map.put("verified_at", previous["verified_at"]) + else + field + end + end) fields = raw_fields @@ -1198,6 +1213,10 @@ defmodule Pleroma.User do def update_and_set_cache(changeset) do with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do + if get_change(changeset, :raw_fields) do + BackgroundWorker.enqueue("verify_fields_links", %{"user_id" => user.id}) + end + set_cache(user) end end @@ -1970,8 +1989,47 @@ defmodule Pleroma.User do maybe_delete_from_db(user) end + def perform(:verify_fields_links, user) do + profile_urls = [user.ap_id] + + fields = + user.raw_fields + |> Enum.map(&verify_field_link(&1, profile_urls)) + + changeset = + user + |> update_changeset(%{raw_fields: fields}) + + with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do + set_cache(user) + end + end + def perform(:set_activation_async, user, status), do: set_activation(user, status) + defp verify_field_link(field, profile_urls) do + verified_at = + with %{"value" => value} <- field, + {:verified_at, nil} <- {:verified_at, Map.get(field, "verified_at")}, + %{scheme: scheme, userinfo: nil, host: host} + when not_empty_string(host) and scheme in ["http", "https"] <- + URI.parse(value), + {:not_idn, true} <- {:not_idn, to_string(:idna.encode(host)) == host}, + attr <- Pleroma.Web.RelMe.maybe_put_rel_me(value, profile_urls) do + if attr == "me" do + CommonUtils.to_masto_date(NaiveDateTime.utc_now()) + end + else + {:verified_at, value} when not_empty_string(value) -> + value + + _ -> + nil + end + + Map.put(field, "verified_at", verified_at) + end + @spec external_users_query() :: Ecto.Query.t() def external_users_query do User.Query.build(%{ @@ -2659,10 +2717,11 @@ defmodule Pleroma.User do # - display name def sanitize_html(%User{} = user, filter) do fields = - Enum.map(user.fields, fn %{"name" => name, "value" => value} -> + Enum.map(user.fields, fn %{"name" => name, "value" => value} = fields -> %{ "name" => name, - "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) + "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly), + "verified_at" => Map.get(fields, "verified_at") } end) diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 794417612..eef1c4f15 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -40,6 +40,11 @@ defmodule Pleroma.Workers.BackgroundWorker do Pleroma.FollowingRelationship.move_following(origin, target) end + def perform(%Job{args: %{"op" => "verify_fields_links", "user_id" => user_id}}) do + user = User.get_by_id(user_id) + User.perform(:verify_fields_links, user) + end + def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do Instance.perform(:delete_instance, host) end -- cgit v1.2.3 From f6fee39e42e633ff4298291dca0db656c92dad81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 23 Dec 2023 15:51:20 +0100 Subject: Add changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec.ex | 1 + .../web/api_spec/operations/instance_operation.ex | 2 +- lib/pleroma/web/mastodon_api/views/instance_view.ex | 18 +++++++++--------- 3 files changed, 11 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 163226ce5..0eedf4aea 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -97,6 +97,7 @@ defmodule Pleroma.Web.ApiSpec do "Frontend managment", "Instance configuration", "Instance documents", + "Instance rule managment", "Invites", "MediaProxy cache", "OAuth application managment", diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index a22eb5bc9..452e56e45 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -48,7 +48,7 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do def rules_operation do %Operation{ - tags: ["Instance"], + tags: ["Instance misc"], summary: "Retrieve list of instance rules", operationId: "InstanceController.rules", responses: %{ diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index fa3726d4a..62c6b9e2e 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -73,15 +73,6 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do }) end - defp common_information(instance) do - %{ - languages: Keyword.get(instance, :languages, ["en"]), - rules: render(__MODULE__, "rules.json"), - title: Keyword.get(instance, :name), - version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})" - } - end - def render("rules.json", _) do Pleroma.Rule.query() |> Pleroma.Repo.all() @@ -95,6 +86,15 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do } end + defp common_information(instance) do + %{ + languages: Keyword.get(instance, :languages, ["en"]), + rules: render(__MODULE__, "rules.json"), + title: Keyword.get(instance, :name), + version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})" + } + end + def features do [ "pleroma_api", -- cgit v1.2.3 From a5f64ffd0cba5e183744cf46506656ba61d36eda Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Fri, 19 Jan 2024 21:28:15 +0000 Subject: =?UTF-8?q?Apply=20lanodan=E2=80=99s=20suggestion=20to=201=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pleroma/web/api_spec/schemas/poll.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index cb2ffdc68..eeb1a5490 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -60,7 +60,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do pleroma: %Schema{ type: :object, properties: %{ - non_anonymous: %Schema{type: :boolean, description: "Is the voters collection public?"} + non_anonymous: %Schema{type: :boolean, description: "Can voters be publicly identified?"} } } }, -- cgit v1.2.3 From def088ce520ffac3e200cbf63b049f1d918d2b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 21 Jan 2024 18:23:24 +0100 Subject: format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/schemas/poll.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index eeb1a5490..20cf5b061 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -60,7 +60,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do pleroma: %Schema{ type: :object, properties: %{ - non_anonymous: %Schema{type: :boolean, description: "Can voters be publicly identified?"} + non_anonymous: %Schema{ + type: :boolean, + description: "Can voters be publicly identified?" + } } } }, -- cgit v1.2.3 From 0de1a7629c5dbf3f6fba69a41bbeff5947558899 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 25 Jan 2024 00:26:55 +0100 Subject: Maps: Add filter_empty_values/1 --- lib/pleroma/maps.ex | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex index 6d586e53e..5020a8ff8 100644 --- a/lib/pleroma/maps.ex +++ b/lib/pleroma/maps.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors +# Copyright © 2017-2024 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Maps do @@ -18,4 +18,17 @@ defmodule Pleroma.Maps do rescue _ -> data end + + def filter_empty_values(data) do + # TODO: Change to Map.filter in Elixir 1.13+ + data + |> Enum.filter(fn + {_k, nil} -> false + {_k, ""} -> false + {_k, []} -> false + {_k, %{} = v} -> Map.keys(v) != [] + {_k, _v} -> true + end) + |> Map.new() + end end -- cgit v1.2.3 From acef2a4a407058c6dfa4e18aaa9920ab1a82eb94 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 25 Jan 2024 00:27:15 +0100 Subject: CommonFixes: Use Maps.filter_empty_values on fix_object_defaults --- lib/pleroma/web/activity_pub/object_validators/common_fixes.ex | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex index 4d9be0bdd..99c62cb4e 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.User @@ -24,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do end def fix_object_defaults(data) do + data = Maps.filter_empty_values(data) + context = Utils.maybe_create_context( data["context"] || data["conversation"] || data["inReplyTo"] || data["id"] -- cgit v1.2.3 From 799891d359363ab66fc0e351ad8140b689027fc4 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 26 Jan 2024 17:10:10 +0100 Subject: Transmogrifier: Cleanup obsolete handling of `"contentMap": null` --- lib/pleroma/web/activity_pub/transmogrifier.ex | 4 ---- 1 file changed, 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index a4cf395f2..80f64cf1e 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -336,10 +336,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_tag(object), do: object - def fix_content_map(%{"contentMap" => nil} = object) do - Map.drop(object, ["contentMap"]) - end - # content map usually only has one language so this will do for now. def fix_content_map(%{"contentMap" => content_map} = object) do content_groups = Map.to_list(content_map) -- cgit v1.2.3 From c6f783c5519cc2cd519be4406366ecbad57a8c40 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 11:01:37 -0500 Subject: Pleroma.Web.ControllerHelper: fix @spec to resolve dialyzer errors lib/pleroma/web/admin_api/controllers/user_controller.ex:333:no_return Function index/2 has no local return. ________________________________________________________________________________ lib/pleroma/web/admin_api/controllers/user_controller.ex:357:unused_fun Function maybe_parse_filters/1 will never be called. ________________________________________________________________________________ lib/pleroma/web/admin_api/controllers/user_controller.ex:366:no_return Function page_params/1 has no local return. ________________________________________________________________________________ lib/pleroma/web/admin_api/controllers/user_controller.ex:368:call The function call will not succeed. Pleroma.Web.ControllerHelper.fetch_integer_param(_params :: any(), :page, 1) breaks the contract (map(), String.t(), integer() | nil) :: integer() | nil --- lib/pleroma/web/controller_helper.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index d3cf372dc..1caf0f7e6 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ControllerHelper do |> json(json) end - @spec fetch_integer_param(map(), String.t(), integer() | nil) :: integer() | nil + @spec fetch_integer_param(map(), String.t() | atom(), integer() | nil) :: integer() | nil def fetch_integer_param(params, name, default \\ nil) do params |> Map.get(name, default) -- cgit v1.2.3 From ed2f8e45e9118177727b22f103f3c20476a891cf Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 11:12:41 -0500 Subject: Pleroma.Web.MastodonAPI.SearchController: fix dialyzer errors Add a separate Pagination.paginate_list/2 function instead of overloading paginate/4 and complicating its matching and @spec --- lib/pleroma/pagination.ex | 9 +++++---- lib/pleroma/web/mastodon_api/controllers/search_controller.ex | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index f12ca2819..8db732cc9 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -61,15 +61,16 @@ defmodule Pleroma.Pagination do |> Repo.all() end - @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()] - def paginate(query, options, method \\ :keyset, table_binding \\ nil) - - def paginate(list, options, _method, _table_binding) when is_list(list) do + @spec paginate_list(list(), keyword()) :: list() + def paginate_list(list, options) do offset = options[:offset] || 0 limit = options[:limit] || 0 Enum.slice(list, offset, limit) end + @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()] + def paginate(query, options, method \\ :keyset, table_binding \\ nil) + def paginate(query, options, :keyset, table_binding) do query |> restrict(:min_id, options, table_binding) diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 7fdf684d2..628aa311b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -156,7 +156,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do tags end - Pleroma.Pagination.paginate(tags, options) + Pleroma.Pagination.paginate_list(tags, options) end defp add_joined_tag(tags) do -- cgit v1.2.3 From 5e8bedcca0ebac6fc92733c4e3a95842c850165b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 11:15:48 -0500 Subject: Pleroma.Web.PleromaAPI.MascotController: fix dialyzer error due to bad error match lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:37:pattern_match The pattern can never match the type. Pattern: {:content_type, _} Type: {:error, _} ________________________________________________________________________________ lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:40:pattern_match The pattern can never match the type. Pattern: {:upload, {:error, _}} Type: {:error, _} --- lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex index 5e412196b..3208cde98 100644 --- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do _ ) do with {:content_type, "image" <> _} <- {:content_type, file.content_type}, - {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do + {_, {:ok, object}} <- {:upload, ActivityPub.upload(file, actor: User.ap_id(user))} do attachment = render_attachment(object) {:ok, _user} = User.mascot_update(user, attachment) -- cgit v1.2.3 From 92992c022d454f9489ce54aa8f6189456435a48a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 11:29:06 -0500 Subject: Pleroma.Web.OAuth.OAuthController: dialyzer error validate_scopes/2 can never receive a map as it is only called in one place with a guard requiring a list lib/pleroma/web/o_auth/o_auth_controller.ex:615:guard_fail The guard test: is_map(_params :: maybe_improper_list()) can never succeed. --- lib/pleroma/web/o_auth/o_auth_controller.ex | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 2bbe5d5fa..47b03215f 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -610,13 +610,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - @spec validate_scopes(App.t(), map() | list()) :: + @spec validate_scopes(App.t(), list()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(%App{} = app, params) when is_map(params) do - requested_scopes = Scopes.fetch_scopes(params, app.scopes) - validate_scopes(app, requested_scopes) - end - defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do Scopes.validate(requested_scopes, app.scopes) end -- cgit v1.2.3 From 97c4d3bcc987b6e60c09ab6e2911661cf43051c3 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 13:12:56 -0500 Subject: Pleroma.Web.Plugs.RateLimiter.Supervisor: dialyzer error lib/pleroma/web/plugs/rate_limiter/supervisor.ex:12:no_return Function init/1 has no local return. --- lib/pleroma/web/plugs/rate_limiter/supervisor.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex index f00f3d95e..5f79a3e3e 100644 --- a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex +++ b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor ] - opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] + opts = [strategy: :one_for_one] Supervisor.init(children, opts) end end -- cgit v1.2.3 From 518a38577b6bf3fdf04036e697d81f98009bbec9 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 13:19:34 -0500 Subject: Fix dialyzer errors due to deprecated usage of put_layout/2 --- lib/pleroma/web/embed_controller.ex | 2 -- lib/pleroma/web/o_status/o_status_controller.ex | 1 - lib/pleroma/web/static_fe/static_fe_controller.ex | 1 - 3 files changed, 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/embed_controller.ex b/lib/pleroma/web/embed_controller.ex index ab0df9c5a..2ca4501a6 100644 --- a/lib/pleroma/web/embed_controller.ex +++ b/lib/pleroma/web/embed_controller.ex @@ -11,8 +11,6 @@ defmodule Pleroma.Web.EmbedController do alias Pleroma.Web.ActivityPub.Visibility - plug(:put_layout, :embed) - def show(conn, %{"id" => id}) do with %Activity{local: true} = activity <- Activity.get_by_id_with_object(id), diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 4f2cf02c3..ee7ef4a5d 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -112,7 +112,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do conn - |> put_layout(:metadata_player) |> put_resp_header("x-frame-options", "ALLOW") |> put_resp_header( "content-security-policy", diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index 3f2dbcd02..012f8e464 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do alias Pleroma.Web.Metadata alias Pleroma.Web.Router.Helpers - plug(:put_layout, :static_fe) plug(:assign_id) @page_keys ["max_id", "min_id", "limit", "since_id", "order"] -- cgit v1.2.3 From f933d24b02472352a2928c2987451ceef7208b34 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 13:34:17 -0500 Subject: Pleroma.Config.DeprecationWarnings: fix type errors detected by gradient lib/pleroma/config/deprecation_warnings.ex: The atom :error on line 278 is expected to have type :ok | nil but it has type :error lib/pleroma/config/deprecation_warnings.ex: The atom :error on line 292 is expected to have type :ok | nil but it has type :error lib/pleroma/config/deprecation_warnings.ex: The atom :error on line 390 is expected to have type :ok | nil but it has type :error lib/pleroma/config/deprecation_warnings.ex: The atom :error on line 413 is expected to have type :ok | nil but it has type :error --- lib/pleroma/config/deprecation_warnings.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index a77923264..d26c5db72 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -256,7 +256,7 @@ defmodule Pleroma.Config.DeprecationWarnings do move_namespace_and_warn(@mrf_config_map, warning_preface) end - @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil + @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil | :error def move_namespace_and_warn(config_map, warning_preface) do warning = Enum.reduce(config_map, "", fn -- cgit v1.2.3 From 7745ee27bcedbe54be0479cd0561b74218acb8fe Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 13:43:07 -0500 Subject: Pleroma.MFA.Totp.provisioning_uri/3: add @spec --- lib/pleroma/mfa/totp.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/mfa/totp.ex b/lib/pleroma/mfa/totp.ex index 429c4b700..96fa8d71d 100644 --- a/lib/pleroma/mfa/totp.ex +++ b/lib/pleroma/mfa/totp.ex @@ -14,6 +14,7 @@ defmodule Pleroma.MFA.TOTP do @doc """ https://github.com/google/google-authenticator/wiki/Key-Uri-Format """ + @spec provisioning_uri(String.t(), String.t(), list()) :: String.t() def provisioning_uri(secret, label, opts \\ []) do query = %{ @@ -27,7 +28,7 @@ defmodule Pleroma.MFA.TOTP do |> URI.encode_query() %URI{scheme: "otpauth", host: "totp", path: "/" <> label, query: query} - |> URI.to_string() + |> to_string() end defp default_period, do: Config.get(@config_ns ++ [:period]) -- cgit v1.2.3 From cccfdde14c6742b7bbad0d4e0e25d4b641c721be Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 13:50:43 -0500 Subject: Pleroma.MFA: fix gradient error lib/pleroma/mfa.ex: The map %{error: msg} on line 80 is expected to have type {:ok, list(binary())} | {:error, String.t()} but it has type %{required(:error) => any()} --- lib/pleroma/mfa.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/mfa.ex b/lib/pleroma/mfa.ex index 01b730c76..ce30cd4ad 100644 --- a/lib/pleroma/mfa.ex +++ b/lib/pleroma/mfa.ex @@ -77,7 +77,7 @@ defmodule Pleroma.MFA do {:ok, codes} else {:error, msg} -> - %{error: msg} + {:error, msg} end end -- cgit v1.2.3 From 15621b72842ad7dc1b1be1a480045498cf815f9b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 13:58:26 -0500 Subject: Pleroma.HTTP.RequestBuilder: fix gradient error lib/pleroma/http/request_builder.ex: The variable key on line 69 is expected to have type String.t() but it has type atom() --- lib/pleroma/http/request_builder.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex index f16fb3b35..0a028a64c 100644 --- a/lib/pleroma/http/request_builder.ex +++ b/lib/pleroma/http/request_builder.ex @@ -54,12 +54,12 @@ defmodule Pleroma.HTTP.RequestBuilder do @doc """ Add optional parameters to the request """ - @spec add_param(Request.t(), atom(), atom(), any()) :: Request.t() + @spec add_param(Request.t(), atom(), atom() | String.t(), any()) :: Request.t() def add_param(request, :query, :query, values), do: %{request | query: values} def add_param(request, :body, :body, value), do: %{request | body: value} - def add_param(request, :body, key, value) do + def add_param(request, :body, key, value) when is_binary(key) do request |> Map.put(:body, Multipart.new()) |> Map.update!( -- cgit v1.2.3 From ac7f2cf105204123c420799bb867005eef14053b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 14:08:29 -0500 Subject: Pleroma Emoji mix task: fix gradient error lib/mix/tasks/pleroma/emoji.ex: The tuple {:cwd, pack_path} on line 114 is expected to have type :cooked | :keep_old_files | :memory | :verbose | {:cwd, list(char())} | {:file_filter, (record(:zip_file) -> boolean())} | {:file_list, list(:file.name())} but it has type {:cwd, binary()} --- lib/mix/tasks/pleroma/emoji.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 537f0715e..8b9c921c8 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -111,7 +111,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do {:ok, _} = :zip.unzip(binary_archive, - cwd: pack_path, + cwd: String.to_charlist(pack_path), file_list: files_to_unzip ) -- cgit v1.2.3 From bff04da0f32becc34921dceec57b3a65231adc07 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 14:08:54 -0500 Subject: Pleroma.Emoji.Pack: fix gradient error lib/pleroma/emoji/pack.ex: The tuple {:cwd, tmp_dir} on line 103 is expected to have type :cooked | :keep_old_files | :memory | :verbose | {:cwd, list(char())} | {:file_filter, (record(:zip_file) -> boolean())} | {:file_list, list(:file.name())} but it has type {:cwd, binary()} --- lib/pleroma/emoji/pack.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 85f7e1877..afc341853 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -100,7 +100,7 @@ defmodule Pleroma.Emoji.Pack do {:ok, _emoji_files} = :zip.unzip( to_charlist(file.path), - [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}] + [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, String.to_charlist(tmp_dir)}] ) {_, updated_pack} = -- cgit v1.2.3 From d42b0eb29bdd1e48afa06d6bcf9eee37bc3a4b08 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 14:15:01 -0500 Subject: Pleroma.Config.DeprecationWarnings: fix gradient errors lib/pleroma/config/deprecation_warnings.ex: The atom :error on line 292 is expected to have type :ok | nil but it has type :error lib/pleroma/config/deprecation_warnings.ex: The function call move_namespace_and_warn( [ {Pleroma.ActivityExpiration, Pleroma.Workers.PurgeExpiredActivity, " * `config :pleroma, Pleroma.ActivityExpiration` is now `config :pleroma, Pleroma.Workers.PurgeExpiredActivity`"} ], warning_preface ) on line 350 is expected to have type :ok | nil but it has type :ok | nil | :error lib/pleroma/config/deprecation_warnings.ex: The function call move_namespace_and_warn( [ {Pleroma.Plugs.RemoteIp, Pleroma.Web.Plugs.RemoteIp, " * `config :pleroma, Pleroma.Plugs.RemoteIp` is now `config :pleroma, Pleroma.Web.Plugs.RemoteIp`"} ], warning_preface ) on line 366 is expected to have type :ok | nil but it has type :ok | nil | :error lib/pleroma/config/deprecation_warnings.ex: The atom :error on line 390 is expected to have type :ok | nil but it has type :error lib/pleroma/config/deprecation_warnings.ex: The atom :error on line 413 is expected to have type :ok | nil but it has type :error --- lib/pleroma/config/deprecation_warnings.ex | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index d26c5db72..58d164dc7 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -256,7 +256,7 @@ defmodule Pleroma.Config.DeprecationWarnings do move_namespace_and_warn(@mrf_config_map, warning_preface) end - @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil | :error + @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | :error def move_namespace_and_warn(config_map, warning_preface) do warning = Enum.reduce(config_map, "", fn @@ -279,7 +279,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_media_proxy_whitelist_config() :: :ok | nil + @spec check_media_proxy_whitelist_config() :: :ok | :error def check_media_proxy_whitelist_config do whitelist = Config.get([:media_proxy, :whitelist]) @@ -340,7 +340,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_activity_expiration_config() :: :ok | nil + @spec check_activity_expiration_config() :: :ok | :error def check_activity_expiration_config do warning_preface = """ !!!DEPRECATION WARNING!!! @@ -356,7 +356,7 @@ defmodule Pleroma.Config.DeprecationWarnings do ) end - @spec check_remote_ip_plug_name() :: :ok | nil + @spec check_remote_ip_plug_name() :: :ok | :error def check_remote_ip_plug_name do warning_preface = """ !!!DEPRECATION WARNING!!! @@ -372,7 +372,7 @@ defmodule Pleroma.Config.DeprecationWarnings do ) end - @spec check_uploaders_s3_public_endpoint() :: :ok | nil + @spec check_uploaders_s3_public_endpoint() :: :ok | :error def check_uploaders_s3_public_endpoint do s3_config = Pleroma.Config.get([Pleroma.Uploaders.S3]) @@ -393,7 +393,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_old_chat_shoutbox() :: :ok | nil + @spec check_old_chat_shoutbox() :: :ok | :error def check_old_chat_shoutbox do instance_config = Pleroma.Config.get([:instance]) chat_config = Pleroma.Config.get([:chat]) || [] -- cgit v1.2.3 From a2c686e16c570791e754af7ed8505e77d29549de Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 31 Jan 2024 14:22:17 -0500 Subject: Pleroma.Filter: fix gradient error lib/pleroma/filter.ex: The clause on line 220 cannot be reached --- lib/pleroma/filter.ex | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex index db88bc021..e827d3cbc 100644 --- a/lib/pleroma/filter.ex +++ b/lib/pleroma/filter.ex @@ -216,9 +216,6 @@ defmodule Pleroma.Filter do :re -> ~r/\b#{phrases}\b/i - - _ -> - nil end end -- cgit v1.2.3 From 04fc4eddaa534185d9784351e70f59f30bc1476f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 4 Feb 2024 19:24:52 -0500 Subject: Fix Rich Media Previews for updated activities The Rich Media Previews were not regenerated when a post was updated due to a cache invalidation issue. They are now cached by the activity id so they can be evicted with the other activity cache objects in the :scrubber_cache. --- lib/pleroma/activity/html.ex | 2 +- lib/pleroma/html.ex | 31 +++++++++++-------------------- lib/pleroma/web/rich_media/helpers.ex | 21 ++++++++++++++++++++- 3 files changed, 32 insertions(+), 22 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex index 706b2d36c..ba284b4d5 100644 --- a/lib/pleroma/activity/html.ex +++ b/lib/pleroma/activity/html.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Activity.HTML do end end - defp add_cache_key_for(activity_id, additional_key) do + def add_cache_key_for(activity_id, additional_key) do current = get_cache_keys_for(activity_id) unless additional_key in current do diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 5bf735c4f..84ff2f129 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -6,8 +6,6 @@ defmodule Pleroma.HTML do # Scrubbers are compiled on boot so they can be configured in OTP releases # @on_load :compile_scrubbers - @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - def compile_scrubbers do dir = Path.join(:code.priv_dir(:pleroma), "scrubbers") @@ -67,27 +65,20 @@ defmodule Pleroma.HTML do end end - def extract_first_external_url_from_object(%{data: %{"content" => content}} = object) + @spec extract_first_external_url_from_object(Pleroma.Object.t()) :: + {:ok, String.t()} | {:error, :no_content} + def extract_first_external_url_from_object(%{data: %{"content" => content}}) when is_binary(content) do - unless object.data["fake"] do - key = "URL|#{object.id}" + url = + content + |> Floki.parse_fragment!() + |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") + |> Enum.take(1) + |> Floki.attribute("href") + |> Enum.at(0) - @cachex.fetch!(:scrubber_cache, key, fn _key -> - {:commit, {:ok, extract_first_external_url(content)}} - end) - else - {:ok, extract_first_external_url(content)} - end + {:ok, url} end def extract_first_external_url_from_object(_), do: {:error, :no_content} - - def extract_first_external_url(content) do - content - |> Floki.parse_fragment!() - |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") - |> Enum.take(1) - |> Floki.attribute("href") - |> Enum.at(0) - end end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 61000bb9b..ee12a9dfb 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) @options [ @@ -71,7 +73,24 @@ defmodule Pleroma.Web.RichMedia.Helpers do def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do with true <- @config_impl.get([:rich_media, :enabled]), %Object{} = object <- Object.normalize(activity, fetch: false) do - fetch_data_for_object(object) + if object.data["fake"] do + fetch_data_for_object(object) + else + key = "URL|#{activity.id}" + + @cachex.fetch!(:scrubber_cache, key, fn _ -> + result = fetch_data_for_object(object) + + cond do + match?(%{page_url: _, rich_media: _}, result) -> + Activity.HTML.add_cache_key_for(activity.id, key) + {:commit, result} + + true -> + {:ignore, %{}} + end + end) + end else _ -> %{} end -- cgit v1.2.3 From 579561e97ba83183022d4bd2658522be6b6ae202 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 4 Feb 2024 23:40:11 -0500 Subject: URI.authority is deprecated --- lib/pleroma/web/rich_media/helpers.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index ee12a9dfb..9d6b8a38b 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -27,8 +27,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do |> parse_uri(page_url) end - defp validate_page_url(%URI{host: host, scheme: "https", authority: authority}) - when is_binary(authority) do + defp validate_page_url(%URI{host: host, scheme: "https"}) do cond do host in @config_impl.get([:rich_media, :ignore_hosts], []) -> :error -- cgit v1.2.3 From 0cc038b67c231090827c1b4e71a32f65ee7c3d88 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 5 Feb 2024 00:09:37 -0500 Subject: Ensure URLs with IP addresses for the host do not generate previews --- lib/pleroma/web/rich_media/helpers.ex | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 9d6b8a38b..1501776d9 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -29,6 +29,9 @@ defmodule Pleroma.Web.RichMedia.Helpers do defp validate_page_url(%URI{host: host, scheme: "https"}) do cond do + Linkify.Parser.ip?(host) -> + :error + host in @config_impl.get([:rich_media, :ignore_hosts], []) -> :error -- cgit v1.2.3 From 6b7b443ff95587b33f4b666e68ed82dc6fb485a5 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 6 Feb 2024 14:34:59 -0500 Subject: Pleroma.Web.RichMedia.Parser: Remove test-specific codepaths Also consolidate Tesla mocks into the HttpRequestMock module. Tests were not exercising the real codepaths. The Rich Media Preview only works with https, but most of these tests were only mocking http. --- lib/pleroma/caching.ex | 3 + lib/pleroma/web/rich_media/parser.ex | 107 +++++++++++++++++------------------ 2 files changed, 54 insertions(+), 56 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/caching.ex b/lib/pleroma/caching.ex index eb0588708..796a465af 100644 --- a/lib/pleroma/caching.ex +++ b/lib/pleroma/caching.ex @@ -8,10 +8,13 @@ defmodule Pleroma.Caching do @callback put(Cachex.cache(), any(), any(), Keyword.t()) :: {Cachex.status(), boolean()} @callback put(Cachex.cache(), any(), any()) :: {Cachex.status(), boolean()} @callback fetch!(Cachex.cache(), any(), function() | nil) :: any() + @callback fetch(Cachex.cache(), any(), function() | nil) :: + {atom(), any()} | {atom(), any(), any()} # @callback del(Cachex.cache(), any(), Keyword.t()) :: {Cachex.status(), boolean()} @callback del(Cachex.cache(), any()) :: {Cachex.status(), boolean()} @callback stream!(Cachex.cache(), any()) :: Enumerable.t() @callback expire_at(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()} + @callback expire(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()} @callback exists?(Cachex.cache(), any()) :: {Cachex.status(), boolean()} @callback execute!(Cachex.cache(), function()) :: any() @callback get_and_update(Cachex.cache(), any(), function()) :: diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 837182d8a..a791b2bab 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -13,70 +13,65 @@ defmodule Pleroma.Web.RichMedia.Parser do def parse(nil), do: {:error, "No URL provided"} - if Pleroma.Config.get(:env) == :test do - @spec parse(String.t()) :: {:ok, map()} | {:error, any()} - def parse(url), do: parse_url(url) - else - @spec parse(String.t()) :: {:ok, map()} | {:error, any()} - def parse(url) do - with {:ok, data} <- get_cached_or_parse(url), - {:ok, _} <- set_ttl_based_on_image(data, url) do - {:ok, data} - end + @spec parse(String.t()) :: {:ok, map()} | {:error, any()} + def parse(url) do + with {:ok, data} <- get_cached_or_parse(url), + {:ok, _} <- set_ttl_based_on_image(data, url) do + {:ok, data} end + end - defp get_cached_or_parse(url) do - case @cachex.fetch(:rich_media_cache, url, fn -> - case parse_url(url) do - {:ok, _} = res -> - {:commit, res} - - {:error, reason} = e -> - # Unfortunately we have to log errors here, instead of doing that - # along with ttl setting at the bottom. Otherwise we can get log spam - # if more than one process was waiting for the rich media card - # while it was generated. Ideally we would set ttl here as well, - # so we don't override it number_of_waiters_on_generation - # times, but one, obviously, can't set ttl for not-yet-created entry - # and Cachex doesn't support returning ttl from the fetch callback. - log_error(url, reason) - {:commit, e} - end - end) do - {action, res} when action in [:commit, :ok] -> - case res do - {:ok, _data} = res -> - res - - {:error, reason} = e -> - if action == :commit, do: set_error_ttl(url, reason) - e - end - - {:error, e} -> - {:error, {:cachex_error, e}} - end + defp get_cached_or_parse(url) do + case @cachex.fetch(:rich_media_cache, url, fn -> + case parse_url(url) do + {:ok, _} = res -> + {:commit, res} + + {:error, reason} = e -> + # Unfortunately we have to log errors here, instead of doing that + # along with ttl setting at the bottom. Otherwise we can get log spam + # if more than one process was waiting for the rich media card + # while it was generated. Ideally we would set ttl here as well, + # so we don't override it number_of_waiters_on_generation + # times, but one, obviously, can't set ttl for not-yet-created entry + # and Cachex doesn't support returning ttl from the fetch callback. + log_error(url, reason) + {:commit, e} + end + end) do + {action, res} when action in [:commit, :ok] -> + case res do + {:ok, _data} = res -> + res + + {:error, reason} = e -> + if action == :commit, do: set_error_ttl(url, reason) + e + end + + {:error, e} -> + {:error, {:cachex_error, e}} end + end - defp set_error_ttl(_url, :body_too_large), do: :ok - defp set_error_ttl(_url, {:content_type, _}), do: :ok + defp set_error_ttl(_url, :body_too_large), do: :ok + defp set_error_ttl(_url, {:content_type, _}), do: :ok - # The TTL is not set for the errors above, since they are unlikely to change - # with time + # The TTL is not set for the errors above, since they are unlikely to change + # with time - defp set_error_ttl(url, _reason) do - ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) - @cachex.expire(:rich_media_cache, url, ttl) - :ok - end + defp set_error_ttl(url, _reason) do + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + @cachex.expire(:rich_media_cache, url, ttl) + :ok + end - defp log_error(url, {:invalid_metadata, data}) do - Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) - end + defp log_error(url, {:invalid_metadata, data}) do + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + end - defp log_error(url, reason) do - Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) - end + defp log_error(url, reason) do + Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) end @doc """ -- cgit v1.2.3 From 9f2319e50dc0516bde4bfa3b117ec4792e553bd2 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 6 Feb 2024 16:54:52 -0500 Subject: RichMedia.Helpers: move the validate_page_url/1 function to the Parser module This will ensure that the page validation happens in Parser.parse/1 so it can be called from anywhere and still filter invalid URLs. --- lib/pleroma/web/rich_media/helpers.ex | 43 -------------------------------- lib/pleroma/web/rich_media/parser.ex | 46 ++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 44 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 1501776d9..a711dc436 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -18,53 +18,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do recv_timeout: 2_000 ] - @spec validate_page_url(URI.t() | binary()) :: :ok | :error - defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld]) - - page_url - |> Linkify.Parser.url?(validate_tld: validate_tld) - |> parse_uri(page_url) - end - - defp validate_page_url(%URI{host: host, scheme: "https"}) do - cond do - Linkify.Parser.ip?(host) -> - :error - - host in @config_impl.get([:rich_media, :ignore_hosts], []) -> - :error - - get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) -> - :error - - true -> - :ok - end - end - - defp validate_page_url(_), do: :error - - defp parse_uri(true, url) do - url - |> URI.parse() - |> validate_page_url - end - - defp parse_uri(_, _), do: :error - - defp get_tld(host) do - host - |> String.split(".") - |> Enum.reverse() - |> hd - end - def fetch_data_for_object(object) do with true <- @config_impl.get([:rich_media, :enabled]), {:ok, page_url} <- HTML.extract_first_external_url_from_object(object), - :ok <- validate_page_url(page_url), {:ok, rich_media} <- Parser.parse(page_url) do %{page_url: page_url, rich_media: rich_media} else diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index a791b2bab..a73fbc4b9 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.RichMedia.Parser do require Logger @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) defp parsers do Pleroma.Config.get([:rich_media, :parsers]) @@ -15,7 +16,8 @@ defmodule Pleroma.Web.RichMedia.Parser do @spec parse(String.t()) :: {:ok, map()} | {:error, any()} def parse(url) do - with {:ok, data} <- get_cached_or_parse(url), + with :ok <- validate_page_url(url), + {:ok, data} <- get_cached_or_parse(url), {:ok, _} <- set_ttl_based_on_image(data, url) do {:ok, data} end @@ -161,4 +163,46 @@ defmodule Pleroma.Web.RichMedia.Parser do end) |> Map.new() end + + @spec validate_page_url(URI.t() | binary()) :: :ok | :error + defp validate_page_url(page_url) when is_binary(page_url) do + validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld]) + + page_url + |> Linkify.Parser.url?(validate_tld: validate_tld) + |> parse_uri(page_url) + end + + defp validate_page_url(%URI{host: host, scheme: "https"}) do + cond do + Linkify.Parser.ip?(host) -> + :error + + host in @config_impl.get([:rich_media, :ignore_hosts], []) -> + :error + + get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) -> + :error + + true -> + :ok + end + end + + defp validate_page_url(_), do: :error + + defp parse_uri(true, url) do + url + |> URI.parse() + |> validate_page_url + end + + defp parse_uri(_, _), do: :error + + defp get_tld(host) do + host + |> String.split(".") + |> Enum.reverse() + |> hd + end end -- cgit v1.2.3 From 0fcdcc2300cc23eee27f1d20bf0a8008581329d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 7 Feb 2024 17:55:52 +0100 Subject: Use User.full_nickname/1 in oauth html template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/templates/o_auth/o_auth/show.html.eex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index 5b38f7142..6bc8eb602 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -13,7 +13,7 @@ <% end %> -- cgit v1.2.3 From 0eca3e38ebd6c5b1e7135275959d984af7acfd26 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 9 Feb 2024 10:36:58 -0500 Subject: Fix Gun connection supervisor logic error This was recently changed to solve a Dialyzer error, but the replacement logic was faulty as "retry" would only be compared to :error and not have its truthiness evaluated. The original logic was also faulty as it returned {:error, :pool_full} even retry was true. It never retried when the pool was full. --- lib/pleroma/gun/connection_pool/worker_supervisor.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex index b2be4ff87..24ad61117 100644 --- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -21,7 +21,9 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do def start_worker(opts, retry \\ false) do case DynamicSupervisor.start_child(__MODULE__, {Pleroma.Gun.ConnectionPool.Worker, opts}) do {:error, :max_children} -> - if Enum.any?([retry, free_pool()], &match?(&1, :error)) do + funs = [fn -> !retry end, fn -> match?(:error, free_pool()) end] + + if Enum.any?(funs, fn fun -> fun.() end) do :telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts}) {:error, :pool_full} else -- cgit v1.2.3 From 8daf19ec0faac26569bf078b140fc4f7ad0d6b2b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 12 Feb 2024 12:39:47 -0600 Subject: Fix notifications index --- lib/pleroma/notification.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 48d467c59..368e609d2 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Notification do where: q.seen == true, select: type(q.id, :string), limit: 1, - order_by: [desc: :id] + order_by: fragment("? desc nulls last", q.id) ) end -- cgit v1.2.3 From 3b82864bccee1af625dd19faed511d5b76f66f9d Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 14 Feb 2024 18:16:54 +0100 Subject: =?UTF-8?q?Config:=20Check=20the=20permissions=20of=20the=20linked?= =?UTF-8?q?=20file=20instead=20of=20the=20symlink=E2=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pleroma/config/release_runtime_provider.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex index 9ec0f975e..351639836 100644 --- a/lib/pleroma/config/release_runtime_provider.ex +++ b/lib/pleroma/config/release_runtime_provider.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do with_runtime_config = if File.exists?(config_path) do # - %File.Stat{mode: mode} = File.lstat!(config_path) + %File.Stat{mode: mode} = File.stat!(config_path) if Bitwise.band(mode, 0o007) > 0 do raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}" -- cgit v1.2.3 From 60ba6fd244b4deb8c69d4cd6b3114dcefeee4075 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 14 Feb 2024 13:25:52 -0500 Subject: MediaProxy RFC compliance --- lib/pleroma/reverse_proxy.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index cc4530010..4d13e51fc 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -8,7 +8,7 @@ defmodule Pleroma.ReverseProxy do ~w(if-unmodified-since if-none-match) ++ @range_headers @resp_cache_headers ~w(etag date last-modified) @keep_resp_headers @resp_cache_headers ++ - ~w(content-length content-type content-disposition content-encoding) ++ + ~w(content-type content-disposition content-encoding) ++ ~w(content-range accept-ranges vary) @default_cache_control_header "public, max-age=1209600" @valid_resp_codes [200, 206, 304] -- cgit v1.2.3 From 9a4c8e2316b59e0c369486c3c2f758162af1b72e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 14 Feb 2024 13:28:32 -0500 Subject: Change some Gun connection pool logs to debug level --- lib/pleroma/telemetry/logger.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex index 92d395394..9998d8185 100644 --- a/lib/pleroma/telemetry/logger.ex +++ b/lib/pleroma/telemetry/logger.ex @@ -59,7 +59,7 @@ defmodule Pleroma.Telemetry.Logger do _, _ ) do - Logger.error(fn -> + Logger.debug(fn -> "Connection pool had to refuse opening a connection to #{key} due to connection limit exhaustion" end) end @@ -81,7 +81,7 @@ defmodule Pleroma.Telemetry.Logger do %{key: key, protocol: :http}, _ ) do - Logger.info(fn -> + Logger.debug(fn -> "Pool worker for #{key}: #{length(clients)} clients are using an HTTP1 connection at the same time, head-of-line blocking might occur." end) end -- cgit v1.2.3 From 64ad451a7b8a2ac89079a1bc32680e9cf08ef24e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 13 Feb 2024 19:21:45 -0500 Subject: Websocket refactor to use Phoenix.Socket.Transport This will make us compatible with Cowboy and Bandit --- lib/phoenix/transports/web_socket/raw.ex | 93 -------- lib/pleroma/web/endpoint.ex | 9 + lib/pleroma/web/mastodon_api/websocket_handler.ex | 256 +++++++++++----------- 3 files changed, 132 insertions(+), 226 deletions(-) delete mode 100644 lib/phoenix/transports/web_socket/raw.ex (limited to 'lib') diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex deleted file mode 100644 index cf4fda79f..000000000 --- a/lib/phoenix/transports/web_socket/raw.ex +++ /dev/null @@ -1,93 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Phoenix.Transports.WebSocket.Raw do - import Plug.Conn, - only: [ - fetch_query_params: 1, - send_resp: 3 - ] - - alias Phoenix.Socket.Transport - - def default_config do - [ - timeout: 60_000, - transport_log: false, - cowboy: Phoenix.Endpoint.CowboyWebSocket - ] - end - - def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do - {_, opts} = handler.__transport__(transport) - - conn = - conn - |> fetch_query_params - |> Transport.transport_log(opts[:transport_log]) - |> Transport.check_origin(handler, endpoint, opts) - - case conn do - %{halted: false} = conn -> - case handler.connect(%{ - endpoint: endpoint, - transport: transport, - options: [serializer: nil], - params: conn.params - }) do - {:ok, socket} -> - {:ok, conn, {__MODULE__, {socket, opts}}} - - :error -> - send_resp(conn, :forbidden, "") - {:error, conn} - end - - _ -> - {:error, conn} - end - end - - def init(conn, _) do - send_resp(conn, :bad_request, "") - {:error, conn} - end - - def ws_init({socket, config}) do - Process.flag(:trap_exit, true) - {:ok, %{socket: socket}, config[:timeout]} - end - - def ws_handle(op, data, state) do - state.socket.handler - |> apply(:handle, [op, data, state]) - |> case do - {op, data} -> - {:reply, {op, data}, state} - - {op, data, state} -> - {:reply, {op, data}, state} - - %{} = state -> - {:ok, state} - - _ -> - {:ok, state} - end - end - - def ws_info({_, _} = tuple, state) do - {:reply, tuple, state} - end - - def ws_info(_tuple, state), do: {:ok, state} - - def ws_close(state) do - ws_handle(:closed, :normal, state) - end - - def ws_terminate(reason, state) do - ws_handle(:closed, reason, state) - end -end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 307fa069e..c56362049 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -9,6 +9,15 @@ defmodule Pleroma.Web.Endpoint do alias Pleroma.Config + socket("/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, + longpoll: false, + websocket: [ + path: "/", + compress: false, + error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []} + ] + ) + socket("/socket", Pleroma.Web.UserSocket, websocket: [ path: "/websocket", diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 07c2b62e3..bb27d806d 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -11,28 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.Web.Streamer alias Pleroma.Web.StreamerView - @behaviour :cowboy_websocket + @behaviour Phoenix.Socket.Transport # Client ping period. @tick :timer.seconds(30) - # Cowboy timeout period. - @timeout :timer.seconds(60) - # Hibernate every X messages - @hibernate_every 100 - - def init(%{qs: qs} = req, state) do - with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), - sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), - access_token <- Map.get(params, "access_token"), - {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket), - {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do - req = - if sec_websocket do - :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) - else - req - end + @impl Phoenix.Socket.Transport + def child_spec(_opts), do: :ignore + + # This only prepares the connection and is not in the process yet + @impl Phoenix.Socket.Transport + def connect(%{params: params} = transport_info) do + with access_token <- Map.get(params, "access_token"), + {:ok, user, oauth_token} <- authenticate_request(access_token), + {:ok, topic} <- + Streamer.get_topic(params["stream"], user, oauth_token, params) do topics = if topic do [topic] @@ -40,41 +33,40 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do [] end - {:cowboy_websocket, req, - %{user: user, topics: topics, oauth_token: oauth_token, count: 0, timer: nil}, - %{idle_timeout: @timeout}} + state = %{ + user: user, + topics: topics, + oauth_token: oauth_token, + count: 0, + timer: nil + } + + {:ok, state} else {:error, :bad_topic} -> - Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") - req = :cowboy_req.reply(404, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} bad topic #{inspect(transport_info)}") + + {:error, :bad_topic} {:error, :unauthorized} -> - Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}") - req = :cowboy_req.reply(401, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} authentication error: #{inspect(transport_info)}") + {:error, :unauthorized} end end - def websocket_init(state) do - Logger.debug( - "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics}" - ) - + # All subscriptions/links and messages cannot be created + # until the processed is launched with init/1 + @impl Phoenix.Socket.Transport + def init(state) do Enum.each(state.topics, fn topic -> Streamer.add_socket(topic, state.oauth_token) end) - {:ok, %{state | timer: timer()}} - end - # Client's Pong frame. - def websocket_handle(:pong, state) do - if state.timer, do: Process.cancel_timer(state.timer) - {:ok, %{state | timer: timer()}} - end + Process.send_after(self(), :ping, @tick) - # We only receive pings for now - def websocket_handle(:ping, state), do: {:ok, state} + {:ok, state} + end - def websocket_handle({:text, text}, state) do + @impl Phoenix.Socket.Transport + def handle_in({text, [opcode: :text]}, state) do with {:ok, %{} = event} <- Jason.decode(text) do handle_client_event(event, state) else @@ -84,50 +76,47 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end - def websocket_handle(frame, state) do + def handle_in(frame, state) do Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") {:ok, state} end - def websocket_info({:render_with_user, view, template, item, topic}, state) do + @impl Phoenix.Socket.Transport + def handle_info({:render_with_user, view, template, item, topic}, state) do user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) unless Streamer.filtered_by_user?(user, item) do - websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user}) + message = view.render(template, item, user, topic) + {:push, {:text, message}, %{state | user: user}} else {:ok, state} end end - def websocket_info({:text, message}, state) do - # If the websocket processed X messages, force an hibernate/GC. - # We don't hibernate at every message to balance CPU usage/latency with RAM usage. - if state.count > @hibernate_every do - {:reply, {:text, message}, %{state | count: 0}, :hibernate} - else - {:reply, {:text, message}, %{state | count: state.count + 1}} - end + def handle_info({:text, text}, state) do + {:push, {:text, text}, state} end - # Ping tick. We don't re-queue a timer there, it is instead queued when :pong is received. - # As we hibernate there, reset the count to 0. - # If the client misses :pong, Cowboy will automatically timeout the connection after - # `@idle_timeout`. - def websocket_info(:tick, state) do - {:reply, :ping, %{state | timer: nil, count: 0}, :hibernate} + def handle_info(:ping, state) do + Process.send_after(self(), :ping, @tick) + + {:push, {:ping, ""}, state} end - def websocket_info(:close, state) do - {:stop, state} + def handle_info(:close, state) do + {:stop, {:closed, 'connection closed by server'}, state} end - # State can be `[]` only in case we terminate before switching to websocket, - # we already log errors for these cases in `init/1`, so just do nothing here - def terminate(_reason, _req, []), do: :ok + def handle_info(msg, state) do + Logger.debug("#{__MODULE__} received info: #{inspect(msg)}") - def terminate(reason, _req, state) do + {:ok, state} + end + + @impl Phoenix.Socket.Transport + def terminate(reason, state) do Logger.debug( - "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)}" + "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)})" ) Enum.each(state.topics, fn topic -> Streamer.remove_socket(topic) end) @@ -135,16 +124,13 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end # Public streams without authentication. - defp authenticate_request(nil, nil) do + defp authenticate_request(nil) do {:ok, nil, nil} end # Authenticated streams. - defp authenticate_request(access_token, sec_websocket) do - token = access_token || sec_websocket - - with true <- is_bitstring(token), - oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token), + defp authenticate_request(access_token) do + with oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), user = %User{} <- User.get_cached_by_id(user_id) do {:ok, user, oauth_token} else @@ -152,36 +138,32 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end - defp timer do - Process.send_after(self(), :tick, @tick) - end - defp handle_client_event(%{"type" => "subscribe", "stream" => _topic} = params, state) do with {_, {:ok, topic}} <- {:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)}, {_, false} <- {:subscribed, topic in state.topics} do Streamer.add_socket(topic, state.oauth_token) - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"})} - ], %{state | topics: [topic | state.topics]}} + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: [topic | state.topics]}} else {:subscribed, true} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"})} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} {:topic, {:error, error}} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "subscribe", - result: "error", - error: error - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "subscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} end end @@ -191,26 +173,26 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {_, true} <- {:subscribed, topic in state.topics} do Streamer.remove_socket(topic) - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"})} - ], %{state | topics: List.delete(state.topics, topic)}} + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: List.delete(state.topics, topic)}} else {:subscribed, false} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"})} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} {:topic, {:error, error}} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "unsubscribe", - result: "error", - error: error - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "unsubscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} end end @@ -219,39 +201,47 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do state ) do with {:auth, nil, nil} <- {:auth, state.user, state.oauth_token}, - {:ok, user, oauth_token} <- authenticate_request(access_token, nil) do - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "success" - })} - ], %{state | user: user, oauth_token: oauth_token}} + {:ok, user, oauth_token} <- authenticate_request(access_token) do + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "success" + }) + + {:reply, :ok, {:text, message}, %{state | user: user, oauth_token: oauth_token}} else {:auth, _, _} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "error", - error: :already_authenticated - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :already_authenticated + }) + + {:reply, :error, {:text, message}, state} _ -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "error", - error: :unauthorized - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :unauthorized + }) + + {:reply, :error, {:text, message}, state} end end defp handle_client_event(params, state) do Logger.error("#{__MODULE__} received unknown event: #{inspect(params)}") - {[], state} + {:ok, state} + end + + def handle_error(conn, :unauthorized) do + Plug.Conn.send_resp(conn, 401, "Unauthorized") + end + + def handle_error(conn, _reason) do + Plug.Conn.send_resp(conn, 404, "Not Found") end end -- cgit v1.2.3 From 86e6d395d931f532b18fccdeb65c300b22fbce8a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 14 Feb 2024 17:54:56 -0500 Subject: Fix atom leak in password digest functionality The value here gets passesd to :crypto.pbkdf2_hmac and it expects one of these atoms: :sha | :sha224 | :sha256 | :sha384 | :sha512 so it will always exist --- lib/pleroma/password/pbkdf2.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/password/pbkdf2.ex b/lib/pleroma/password/pbkdf2.ex index 92e9e1952..9c6d2e381 100644 --- a/lib/pleroma/password/pbkdf2.ex +++ b/lib/pleroma/password/pbkdf2.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Password.Pbkdf2 do iterations = String.to_integer(iterations) - digest = String.to_atom(digest) + digest = String.to_existing_atom(digest) binary_hash = KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64) -- cgit v1.2.3 From 91c83a82a052ec73c82b9b5576fd5b05c7dc8a74 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 14 Feb 2024 17:58:36 -0500 Subject: Fix atom leak in background worker The only permitted values are "blocks_import", "follow_import", "mutes_import" of which we already have the equivalent atoms defined. --- lib/pleroma/workers/background_worker.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 794417612..7a2210dc1 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Workers.BackgroundWorker do def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) when op in ["blocks_import", "follow_import", "mutes_import"] do user = User.get_cached_by_id(user_id) - {:ok, User.Import.perform(String.to_atom(op), user, identifiers)} + {:ok, User.Import.perform(String.to_existing_atom(op), user, identifiers)} end def perform(%Job{ -- cgit v1.2.3 From 7e99d0619d02835fab66134f14dd99fb4c313d26 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 12 Feb 2024 17:25:02 -0500 Subject: Force more frequent full_sweep GC runs on the Websocket processes Websocket processes seem to be the primary culprit for Binary memory allocation bloat. --- lib/pleroma/web/endpoint.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 51a77ed60..2e2104904 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -14,7 +14,8 @@ defmodule Pleroma.Web.Endpoint do websocket: [ path: "/", compress: false, - error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []} + error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []}, + fullsweep_after: 20 ] ) @@ -27,7 +28,8 @@ defmodule Pleroma.Web.Endpoint do ], timeout: 60_000, transport_log: false, - compress: false + compress: false, + fullsweep_after: 20 ], longpoll: false ) -- cgit v1.2.3 From 7d624c4750dcf53d48cc65874c832513f2b03fbc Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 20 Feb 2024 08:45:48 +0100 Subject: StealEmojiPolicy: Sanitize shortcodes Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3245 --- lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 237dfefa5..fa6b595ea 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -36,6 +36,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do extension = if extension == "", do: ".png", else: extension + shortcode = Path.basename(shortcode) file_path = Path.join(emoji_dir_path, shortcode <> extension) case File.write(file_path, response.body) do @@ -78,6 +79,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do new_emojis = foreign_emojis |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) + |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\"]) end) |> Enum.filter(fn {shortcode, _url} -> reject_emoji? = [:mrf_steal_emoji, :rejected_shortcodes] -- cgit v1.2.3 From ac55764599c054a03299470d92fd0d274509a024 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 22 Feb 2024 14:07:46 -0500 Subject: Gun Connection Pool was not attempting to free a connection and retry once if the pool was full. --- lib/pleroma/gun/connection_pool/worker_supervisor.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex index 24ad61117..edaffcc25 100644 --- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do def start_worker(opts, retry \\ false) do case DynamicSupervisor.start_child(__MODULE__, {Pleroma.Gun.ConnectionPool.Worker, opts}) do {:error, :max_children} -> - funs = [fn -> !retry end, fn -> match?(:error, free_pool()) end] + funs = [fn -> retry end, fn -> match?(:error, free_pool()) end] if Enum.any?(funs, fn fun -> fun.() end) do :telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts}) -- cgit v1.2.3 From 72fc41d89196dc76e6c2c1c6213e10cbaadd7d3c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 22 Feb 2024 14:11:02 -0500 Subject: Prevent publisher jobs from erroring if the connection pool is full A full pool is a soft-error. Snooze the job for 30 seconds and try again. --- lib/pleroma/web/activity_pub/publisher.ex | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index c27612697..9e7d00519 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -129,6 +129,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do _ -> {:error, e} end + {:error, :pool_full} -> + Logger.debug("Publisher snoozing worker job due to full connection pool") + {:snooze, 30} + e -> unless params[:unreachable_since], do: Instances.set_unreachable(inbox) Logger.metadata(activity: id, inbox: inbox) -- cgit v1.2.3 From 6af6a9704d2d0a04a43967805368eed419b9dc67 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 23 Feb 2024 01:57:43 +0100 Subject: RemoteFetcherWorker: Make sure {:error, _} is returned on failure Otherwise jobs are considered a success. --- lib/pleroma/workers/remote_fetcher_worker.ex | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex index d526a99cb..c26418483 100644 --- a/lib/pleroma/workers/remote_fetcher_worker.ex +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -22,8 +22,11 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do {:error, :allowed_depth} -> {:discard, :allowed_depth} - _ -> - :error + {:error, _} = e -> + e + + e -> + {:error, e} end end -- cgit v1.2.3 From f4e48bc53ee5ffeb9e7387219c0d46dc33d04521 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 23 Feb 2024 11:12:10 -0500 Subject: Rename variable to make the worker retry logic easier to read The boolean value matches the intent of the "last_attempt" variable name now --- lib/pleroma/gun/connection_pool/worker_supervisor.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex index edaffcc25..eb83962d8 100644 --- a/lib/pleroma/gun/connection_pool/worker_supervisor.ex +++ b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -18,10 +18,10 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do ) end - def start_worker(opts, retry \\ false) do + def start_worker(opts, last_attempt \\ false) do case DynamicSupervisor.start_child(__MODULE__, {Pleroma.Gun.ConnectionPool.Worker, opts}) do {:error, :max_children} -> - funs = [fn -> retry end, fn -> match?(:error, free_pool()) end] + funs = [fn -> last_attempt end, fn -> match?(:error, free_pool()) end] if Enum.any?(funs, fn fun -> fun.() end) do :telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts}) -- cgit v1.2.3 From acb9e46074346ad28ad6444a170bdd5e00c74910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 27 Feb 2024 13:25:26 +0100 Subject: Add some missing fields to instanceV2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 7f73917d6..e4c6c81e1 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -63,7 +63,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do registrations: %{ enabled: Keyword.get(instance, :registrations_open), approval_required: Keyword.get(instance, :account_approval_required), - message: nil + message: nil, + url: nil }, contact: %{ email: Keyword.get(instance, :email), @@ -78,7 +79,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do %{ title: Keyword.get(instance, :name), version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", - languages: Keyword.get(instance, :languages, ["en"]) + languages: Keyword.get(instance, :languages, ["en"]), + rules: [] } end @@ -170,13 +172,17 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do defp configuration do %{ + accounts: %{ + max_featured_tags: 0 + }, statuses: %{ max_characters: Config.get([:instance, :limit]), max_media_attachments: Config.get([:instance, :max_media_attachments]) }, media_attachments: %{ image_size_limit: Config.get([:instance, :upload_limit]), - video_size_limit: Config.get([:instance, :upload_limit]) + video_size_limit: Config.get([:instance, :upload_limit]), + supported_mime_types: ["application/octet-stream"] }, polls: %{ max_options: Config.get([:instance, :poll_limits, :max_options]), @@ -190,7 +196,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do defp configuration2 do configuration() |> Map.merge(%{ - urls: %{streaming: Pleroma.Web.Endpoint.websocket_url()} + urls: %{ + streaming: Pleroma.Web.Endpoint.websocket_url(), + status: Config.get([:instance, :status_page]) + }, + vapid: %{ + public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } }) end -- cgit v1.2.3 From d415686bb9248e1c5b8913b7c3b96f74e9b8f230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 26 Feb 2024 23:45:02 +0100 Subject: Allow to group bookmarks in folders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/bookmark.ex | 31 ++++- lib/pleroma/bookmark_folder.ex | 115 +++++++++++++++++++ lib/pleroma/web/api_spec.ex | 3 +- .../pleroma_bookmark_folder_operation.ex | 125 +++++++++++++++++++++ .../web/api_spec/operations/status_operation.ex | 22 +++- .../web/api_spec/schemas/bookmark_folder.ex | 26 +++++ .../mastodon_api/controllers/status_controller.ex | 17 ++- lib/pleroma/web/mastodon_api/views/status_view.ex | 28 ++++- .../controllers/bookmark_folder_controller.ex | 68 +++++++++++ .../web/pleroma_api/views/bookmark_folder_view.ex | 44 ++++++++ lib/pleroma/web/router.ex | 5 + 11 files changed, 468 insertions(+), 16 deletions(-) create mode 100644 lib/pleroma/bookmark_folder.ex create mode 100644 lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/bookmark_folder.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex create mode 100644 lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex (limited to 'lib') diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex index b83d72446..1a2a63b82 100644 --- a/lib/pleroma/bookmark.ex +++ b/lib/pleroma/bookmark.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Bookmark do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.BookmarkFolder alias Pleroma.Repo alias Pleroma.User @@ -18,33 +19,46 @@ defmodule Pleroma.Bookmark do schema "bookmarks" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) + belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType) timestamps() end @spec create(Ecto.UUID.t(), Ecto.UUID.t()) :: {:ok, Bookmark.t()} | {:error, Ecto.Changeset.t()} - def create(user_id, activity_id) do + def create(user_id, activity_id, folder_id \\ nil) do attrs = %{ user_id: user_id, - activity_id: activity_id + activity_id: activity_id, + folder_id: folder_id } %Bookmark{} - |> cast(attrs, [:user_id, :activity_id]) + |> cast(attrs, [:user_id, :activity_id, :folder_id]) |> validate_required([:user_id, :activity_id]) |> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index) - |> Repo.insert() + |> Repo.insert( + on_conflict: [set: [folder_id: folder_id]], + conflict_target: [:user_id, :activity_id] + ) end @spec for_user_query(Ecto.UUID.t()) :: Ecto.Query.t() - def for_user_query(user_id) do + def for_user_query(user_id, folder_id \\ nil) do Bookmark |> where(user_id: ^user_id) + |> maybe_filter_by_folder(folder_id) |> join(:inner, [b], activity in assoc(b, :activity)) |> preload([b, a], activity: a) end + defp maybe_filter_by_folder(query, nil), do: query + + defp maybe_filter_by_folder(query, folder_id) do + query + |> where(folder_id: ^folder_id) + end + def get(user_id, activity_id) do Bookmark |> where(user_id: ^user_id) @@ -62,4 +76,11 @@ defmodule Pleroma.Bookmark do |> Repo.one() |> Repo.delete() end + + def set_folder(bookmark, folder_id) do + bookmark + |> cast(%{folder_id: folder_id}, [:folder_id]) + |> validate_required([:folder_id]) + |> Repo.update() + end end diff --git a/lib/pleroma/bookmark_folder.ex b/lib/pleroma/bookmark_folder.ex new file mode 100644 index 000000000..14d37e197 --- /dev/null +++ b/lib/pleroma/bookmark_folder.ex @@ -0,0 +1,115 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.BookmarkFolder do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.BookmarkFolder + alias Pleroma.Emoji + alias Pleroma.Repo + alias Pleroma.User + + @type t :: %__MODULE__{} + @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} + + schema "bookmark_folders" do + field(:name, :string) + field(:emoji, :string) + + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + + timestamps() + end + + def get_by_id(id), do: Repo.get_by(BookmarkFolder, id: id) + + def create(user_id, name, emoji \\ nil) do + %BookmarkFolder{} + |> cast( + %{ + user_id: user_id, + name: name, + emoji: emoji + }, + [:user_id, :name, :emoji] + ) + |> validate_required([:user_id, :name]) + |> fix_emoji() + |> validate_emoji() + |> unique_constraint([:user_id, :name]) + |> Repo.insert() + end + + def update(folder_id, name, emoji \\ nil) do + get_by_id(folder_id) + |> cast( + %{ + name: name, + emoji: emoji + }, + [:name, :emoji] + ) + |> fix_emoji() + |> validate_emoji() + |> unique_constraint([:user_id, :name]) + |> Repo.update() + end + + defp fix_emoji(changeset) do + with {:emoji_field, emoji} when is_binary(emoji) <- + {:emoji_field, get_field(changeset, :emoji)}, + {:fixed_emoji, emoji} <- + {:fixed_emoji, + emoji + |> Pleroma.Emoji.fully_qualify_emoji() + |> Pleroma.Emoji.maybe_quote()} do + put_change(changeset, :emoji, emoji) + else + {:emoji_field, _} -> changeset + end + end + + defp validate_emoji(changeset) do + validate_change(changeset, :emoji, fn + :emoji, nil -> + [] + + :emoji, emoji -> + if Emoji.unicode?(emoji) or valid_local_custom_emoji?(emoji) do + [] + else + [emoji: "Invalid emoji"] + end + end) + end + + defp valid_local_custom_emoji?(emoji) do + with %{file: _path} <- Emoji.get(emoji) do + true + else + _ -> false + end + end + + def delete(folder_id) do + BookmarkFolder + |> Repo.get_by(id: folder_id) + |> Repo.delete() + end + + def for_user(user_id) do + BookmarkFolder + |> where(user_id: ^user_id) + |> Repo.all() + end + + def belongs_to_user?(folder_id, user_id) do + BookmarkFolder + |> where(id: ^folder_id, user_id: ^user_id) + |> Repo.exists?() + end +end diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 3588608f2..10d221571 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -137,7 +137,8 @@ defmodule Pleroma.Web.ApiSpec do "Scheduled statuses", "Search", "Status actions", - "Media attachments" + "Media attachments", + "Bookmark folders" ] }, %{ diff --git a/lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex new file mode 100644 index 000000000..eaa683125 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex @@ -0,0 +1,125 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaBookmarkFolderOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.BookmarkFolder + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + + import Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(any()) :: any() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "All bookmark folders", + security: [%{"oAuth" => ["read:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.index", + responses: %{ + 200 => + Operation.response("Array of Bookmark Folders", "application/json", %Schema{ + type: :array, + items: BookmarkFolder + }) + } + } + end + + def create_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "Create a bookmark folder", + security: [%{"oAuth" => ["write:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.create", + requestBody: request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder), + 422 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "Update a bookmark folder", + security: [%{"oAuth" => ["write:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.update", + parameters: [id_param()], + requestBody: request_body("Parameters", update_request(), required: true), + responses: %{ + 200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError), + 422 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "Delete a bookmark folder", + security: [%{"oAuth" => ["write:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.delete", + parameters: [id_param()], + responses: %{ + 200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + title: "BookmarkFolderCreateRequest", + type: :object, + properties: %{ + name: %Schema{ + type: :string, + description: "Folder name" + }, + emoji: %Schema{ + type: :string, + nullable: true, + description: "Folder emoji" + } + } + } + end + + defp update_request do + %Schema{ + title: "BookmarkFolderUpdateRequest", + type: :object, + properties: %{ + name: %Schema{ + type: :string, + nullable: true, + description: "Folder name" + }, + emoji: %Schema{ + type: :string, + nullable: true, + description: "Folder emoji" + } + } + } + end + + def id_param do + Operation.parameter(:id, :path, FlakeID.schema(), "Bookmark Folder ID", + example: "9umDrYheeY451cQnEe", + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index ef4e34044..1717c68c8 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -256,6 +256,18 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do description: "Privately bookmark a status", operationId: "StatusController.bookmark", parameters: [id_param()], + requestBody: + request_body("Parameters", %Schema{ + title: "StatusUpdateRequest", + type: :object, + properties: %{ + folder_id: %Schema{ + nullable: true, + allOf: [FlakeID], + description: "ID of bookmarks folder, if any" + } + } + }), responses: %{ 200 => status_response() } @@ -430,7 +442,15 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do summary: "Bookmarked statuses", description: "Statuses the user has bookmarked", operationId: "StatusController.bookmarks", - parameters: pagination_params(), + parameters: [ + Operation.parameter( + :folder_id, + :query, + FlakeID.schema(), + "If provided, only display bookmarks from given folder" + ) + | pagination_params() + ], security: [%{"oAuth" => ["read:bookmarks"]}], responses: %{ 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses()) diff --git a/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex new file mode 100644 index 000000000..e8b4f43b7 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.BookmarkFolder do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "BookmarkFolder", + description: "Response schema for a bookmark folder", + type: :object, + properties: %{ + id: FlakeID, + name: %Schema{type: :string, description: "Folder name"}, + emoji: %Schema{type: :string, description: "Folder emoji", nullable: true} + }, + example: %{ + "id" => "9toJCu5YZW7O7gfvH6", + "name" => "Read later", + "emoji" => nil + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 5aa7bddf0..4f6de8a00 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.BookmarkFolder alias Pleroma.Object alias Pleroma.Repo alias Pleroma.ScheduledActivity @@ -411,13 +412,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "POST /api/v1/statuses/:id/bookmark" def bookmark( - %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: body_params, params: %{id: id}}} + } = conn, _ ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), - {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do + folder_id <- Map.get(body_params, :folder_id, nil), + folder_id <- + if(folder_id && BookmarkFolder.belongs_to_user?(folder_id, user.id), + do: folder_id, + else: nil + ), + {:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end @@ -573,10 +583,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do @doc "GET /api/v1/bookmarks" def bookmarks(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do user = User.get_cached_by_id(user.id) + folder_id = Map.get(params, :folder_id) bookmarks = user.id - |> Bookmark.for_user_query() + |> Bookmark.for_user_query(folder_id) |> Pleroma.Pagination.fetch_paginated(params) activities = diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 6303e72ce..e464f60dc 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -184,7 +184,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil + bookmark = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) + + bookmark_folder = + if bookmark != nil do + bookmark.folder_id + else + nil + end mentions = activity.recipients @@ -213,7 +220,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favourites_count: 0, reblogged: reblogged?(reblogged_parent_activity, opts[:for]), favourited: present?(favorited), - bookmarked: present?(bookmarked), + bookmarked: present?(bookmark), muted: false, pinned: pinned?, sensitive: false, @@ -227,7 +234,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do emojis: [], pleroma: %{ local: activity.local, - pinned_at: pinned_at + pinned_at: pinned_at, + bookmark_folder: bookmark_folder } } end @@ -264,7 +272,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil + bookmark = Activity.get_bookmark(activity, opts[:for]) + + bookmark_folder = + if bookmark != nil do + bookmark.folder_id + else + nil + end client_posted_this_activity = opts[:for] && user.id == opts[:for].id @@ -418,7 +433,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do favourites_count: like_count, reblogged: reblogged?(activity, opts[:for]), favourited: present?(favorited), - bookmarked: present?(bookmarked), + bookmarked: present?(bookmark), muted: muted, pinned: pinned?, sensitive: sensitive, @@ -448,7 +463,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do emoji_reactions: emoji_reactions, parent_visible: visible_for_user?(reply_to, opts[:for]), pinned_at: pinned_at, - quotes_count: object.data["quotesCount"] || 0 + quotes_count: object.data["quotesCount"] || 0, + bookmark_folder: bookmark_folder } } end diff --git a/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex new file mode 100644 index 000000000..6d6e2e940 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.BookmarkFolderController do + use Pleroma.Web, :controller + + alias Pleroma.BookmarkFolder + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + # Note: scope not present in Mastodon: read:bookmarks + plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :index) + + # Note: scope not present in Mastodon: write:bookmarks + plug( + OAuthScopesPlug, + %{scopes: ["write:bookmarks"]} when action in [:create, :update, :delete] + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBookmarkFolderOperation + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + def index(%{assigns: %{user: user}} = conn, _params) do + with folders <- BookmarkFolder.for_user(user.id) do + conn + |> render("index.json", %{folders: folders, as: :folder}) + end + end + + def create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn, + _ + ) do + with {:ok, folder} <- BookmarkFolder.create(user.id, params[:name], params[:emoji]) do + render(conn, "show.json", folder: folder) + end + end + + def update( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: params, params: %{id: id}}} + } = conn, + _ + ) do + with true <- BookmarkFolder.belongs_to_user?(id, user.id), + {:ok, folder} <- BookmarkFolder.update(id, params[:name], params[:emoji]) do + render(conn, "show.json", folder: folder) + else + false -> {:error, :forbidden} + end + end + + def delete( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do + with true <- BookmarkFolder.belongs_to_user?(id, user.id), + {:ok, folder} <- BookmarkFolder.delete(id) do + render(conn, "show.json", folder: folder) + else + false -> {:error, :forbidden} + end + end +end diff --git a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex new file mode 100644 index 000000000..fc6ad59d0 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do + use Pleroma.Web, :view + + alias Pleroma.BookmarkFolder + alias Pleroma.Emoji + alias Pleroma.Web.Endpoint + + def render("show.json", %{folder: %BookmarkFolder{} = folder}) do + %{ + id: folder.id |> to_string(), + name: folder.name, + emoji: get_emoji(folder.emoji), + source: %{ + emoji: folder.emoji + } + } + end + + def render("index.json", %{folders: folders} = opts) do + render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders)) + end + + defp get_emoji(nil) do + nil + end + + defp get_emoji(emoji) do + if Emoji.unicode?(emoji) do + emoji + else + emoji = Emoji.get(emoji) + + if emoji != nil do + Endpoint.url() |> URI.merge(emoji.relative_url) |> to_string() + else + nil + end + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8ba845364..4fe0cb02f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -580,6 +580,11 @@ defmodule Pleroma.Web.Router do get("/backups", BackupController, :index) post("/backups", BackupController, :create) + + get("/bookmark_folders", BookmarkFolderController, :index) + post("/bookmark_folders", BookmarkFolderController, :create) + patch("/bookmark_folders/:id", BookmarkFolderController, :update) + delete("/bookmark_folders/:id", BookmarkFolderController, :delete) end scope [] do -- cgit v1.2.3 From 9cfa4e67b11e5a1a7d09330581383dc67fcf6fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 1 Mar 2024 18:14:31 +0100 Subject: Add ForceMention mrf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/activity_pub/mrf/force_mention.ex | 59 +++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 lib/pleroma/web/activity_pub/mrf/force_mention.ex (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/mrf/force_mention.ex b/lib/pleroma/web/activity_pub/mrf/force_mention.ex new file mode 100644 index 000000000..3853489fc --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/force_mention.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do + require Pleroma.Constants + + alias Pleroma.Config + alias Pleroma.Object + alias Pleroma.User + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + defp get_author(url) do + with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false), + %User{ap_id: ap_id, nickname: nickname} <- User.get_cached_by_ap_id(actor) do + %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"} + else + _ -> nil + end + end + + defp prepend_author(tags, _, false), do: tags + + defp prepend_author(tags, nil, _), do: tags + + defp prepend_author(tags, url, _) do + actor = get_author(url) + + if not is_nil(actor) do + [actor | tags] + else + tags + end + end + + @impl true + def filter(%{"type" => "Create", "object" => %{"tag" => tag} = object} = activity) do + tag = + tag + |> prepend_author( + object["inReplyTo"], + Config.get([:mrf_force_mention, :mention_parent, true]) + ) + |> prepend_author( + object["quoteUrl"], + Config.get([:mrf_force_mention, :mention_quoted, true]) + ) + |> Enum.uniq() + + {:ok, put_in(activity["object"]["tag"], tag)} + end + + @impl true + def filter(object), do: {:ok, object} + + @impl true + def describe, do: {:ok, %{}} +end -- cgit v1.2.3 From 6ad4acea50ab0ac682f0f207d60a640a819c7748 Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Sat, 2 Mar 2024 18:09:08 +0900 Subject: Consider a case when inbox is nil --- lib/pleroma/web/activity_pub/publisher.ex | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 9e7d00519..6267d0760 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -159,17 +159,20 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end defp should_federate?(inbox, public) do - if public do - true - else - %{host: host} = URI.parse(inbox) + cond do + inbox == nil -> + false + public -> + true + true -> + %{host: host} = URI.parse(inbox) - quarantined_instances = - Config.get([:instance, :quarantined_instances], []) - |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() - |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() + quarantined_instances = + Config.get([:instance, :quarantined_instances], []) + |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() + |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() - !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) + !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) end end -- cgit v1.2.3 From 0242c1f691fffd6c45a2b33763a4a7c25f02e65f Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Sat, 2 Mar 2024 18:34:12 +0900 Subject: fmt --- lib/pleroma/web/activity_pub/publisher.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 6267d0760..f4665275c 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -162,8 +162,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do cond do inbox == nil -> false + public -> true + true -> %{host: host} = URI.parse(inbox) -- cgit v1.2.3 From 1422082bf238b09927e036f4de9deebb164e0653 Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 7 Mar 2024 04:43:56 +0000 Subject: Apply ledyba's suggestion(s) to 1 file(s) --- lib/pleroma/web/activity_pub/publisher.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index f4665275c..792c6a1c5 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -160,7 +160,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do defp should_federate?(inbox, public) do cond do - inbox == nil -> + is_nil(inbox) -> false public -> -- cgit v1.2.3 From 47ce33a90d09878d5231ce9654853ebcd1b88c08 Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 7 Mar 2024 11:55:31 +0000 Subject: Apply tusooa's suggestion --- lib/pleroma/user.ex | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d81aa5252..ecf537475 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2015,10 +2015,8 @@ defmodule Pleroma.User do when not_empty_string(host) and scheme in ["http", "https"] <- URI.parse(value), {:not_idn, true} <- {:not_idn, to_string(:idna.encode(host)) == host}, - attr <- Pleroma.Web.RelMe.maybe_put_rel_me(value, profile_urls) do - if attr == "me" do - CommonUtils.to_masto_date(NaiveDateTime.utc_now()) - end + "me" <- Pleroma.Web.RelMe.maybe_put_rel_me(value, profile_urls) do + CommonUtils.to_masto_date(NaiveDateTime.utc_now()) else {:verified_at, value} when not_empty_string(value) -> value -- cgit v1.2.3 From 54ff7234b9b99ecb5b181d255369f17b5520ace8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 7 Mar 2024 17:38:21 -0500 Subject: Fix ffmpeg framegrabs with Exile --- lib/pleroma/helpers/media_helper.ex | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 1a414b37f..7864296fa 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -40,28 +40,32 @@ defmodule Pleroma.Helpers.MediaHelper do end # Note: video thumbnail is intentionally not resized (always has original dimensions) + @spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()} def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), {:ok, env} <- HTTP.get(url, [], pool: :media), {:ok, pid} <- StringIO.open(env.body) do body_stream = IO.binstream(pid, 1) - Exile.stream!( - [ - executable, - "-i", - "pipe:0", - "-vframes", - "1", - "-f", - "mjpeg", - "pipe:1" - ], - input: body_stream, - ignore_epipe: true, - stderr: :disable - ) - |> Enum.into(<<>>) + result = + Exile.stream!( + [ + executable, + "-i", + "pipe:0", + "-vframes", + "1", + "-f", + "mjpeg", + "pipe:1" + ], + input: body_stream, + ignore_epipe: true, + stderr: :disable + ) + |> Enum.into(<<>>) + + {:ok, result} else nil -> {:error, {:ffmpeg, :command_not_found}} {:error, _} = error -> error -- cgit v1.2.3 From bb0b17f4d9a235f79b4b6fabdc5e3dbe472cd259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 7 Mar 2024 20:14:05 +0100 Subject: Include following/followers in backups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/user/backup.ex | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index b7f00bbf7..65e0baccd 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -196,7 +196,14 @@ defmodule Pleroma.User.Backup do end end - @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] + @files [ + 'actor.json', + 'outbox.json', + 'likes.json', + 'bookmarks.json', + 'followers.json', + 'following.json' + ] @spec export(Pleroma.User.Backup.t(), pid()) :: {:ok, String.t()} | :error def export(%__MODULE__{} = backup, caller_pid) do backup = Repo.preload(backup, :user) @@ -207,6 +214,8 @@ defmodule Pleroma.User.Backup do :ok <- statuses(dir, backup.user, caller_pid), :ok <- likes(dir, backup.user, caller_pid), :ok <- bookmarks(dir, backup.user, caller_pid), + :ok <- followers(dir, backup.user, caller_pid), + :ok <- following(dir, backup.user, caller_pid), {:ok, zip_path} <- :zip.create(backup.file_name, @files, cwd: dir), {:ok, _} <- File.rm_rf(dir) do {:ok, zip_path} @@ -357,6 +366,16 @@ defmodule Pleroma.User.Backup do caller_pid ) end + + defp followers(dir, user, caller_pid) do + User.get_followers_query(user) + |> write(dir, "followers", fn a -> {:ok, a.ap_id} end, caller_pid) + end + + defp following(dir, user, caller_pid) do + User.get_friends_query(user) + |> write(dir, "following", fn a -> {:ok, a.ap_id} end, caller_pid) + end end defmodule Pleroma.User.Backup.ProcessorAPI do -- cgit v1.2.3 From 8b651fab1d711a18e8046271a83bcdd756a26670 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 15 Mar 2024 16:20:16 +0100 Subject: AttachmentValidator: Set "Link" as default type --- .../web/activity_pub/object_validators/attachment_validator.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex index 398020bff..72975f348 100644 --- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex @@ -12,13 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do @primary_key false embedded_schema do field(:id, :string) - field(:type, :string) + field(:type, :string, default: "Link") field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream") field(:name, :string) field(:blurhash, :string) embeds_many :url, UrlObjectValidator, primary_key: false do - field(:type, :string) + field(:type, :string, default: "Link") field(:href, ObjectValidators.Uri) field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream") field(:width, :integer) -- cgit v1.2.3 From 48c22a67dec0b55a1bc48ffc4229352248bceb26 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Fri, 15 Mar 2024 16:21:23 +0100 Subject: QuestionOptionsValidator: set default AS types --- .../web/activity_pub/object_validators/question_options_validator.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex index 541945fa4..8d7f7b9fa 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_options_validator.ex @@ -14,10 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator do embeds_one :replies, Replies, primary_key: false do field(:totalItems, :integer) - field(:type, :string) + field(:type, :string, default: "Collection") end - field(:type, :string) + field(:type, :string, default: "Note") end def changeset(struct, data) do -- cgit v1.2.3 From caf855cf9cb9a5cadd2519237c9e7916007f4850 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 17 Mar 2024 16:57:45 +0400 Subject: ActivityPub.Publisher: Don't try federating if a user doesn't have an inbox. --- lib/pleroma/web/activity_pub/publisher.ex | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 792c6a1c5..a42b4844e 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -158,24 +158,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end end - defp should_federate?(inbox, public) do - cond do - is_nil(inbox) -> - false + def should_federate?(nil, _), do: false + def should_federate?(_, true), do: true - public -> - true + def should_federate?(inbox, _) do + %{host: host} = URI.parse(inbox) - true -> - %{host: host} = URI.parse(inbox) + quarantined_instances = + Config.get([:instance, :quarantined_instances], []) + |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() + |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() - quarantined_instances = - Config.get([:instance, :quarantined_instances], []) - |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() - |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() - - !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) - end + !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) end @spec recipients(User.t(), Activity.t()) :: [[User.t()]] -- cgit v1.2.3 From a48f5f860e41ef8a04a0184d2c28cb35a86bbaaa Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Wed, 13 Mar 2024 13:46:37 +0100 Subject: Notifications: filter on users rather than activities --- lib/pleroma/following_relationship.ex | 10 +++++----- lib/pleroma/notification.ex | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 15664c876..f38c2fce9 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -241,13 +241,13 @@ defmodule Pleroma.FollowingRelationship do end @doc """ - For a query with joined activity, - keeps rows where activity's actor is followed by user -or- is NOT domain-blocked by user. + For a query with joined activity's actor, + keeps rows where actor is followed by user -or- is NOT domain-blocked by user. """ def keep_following_or_not_domain_blocked(query, user) do where( query, - [_, activity], + [_, user_actor: user_actor], fragment( # "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)" """ @@ -255,9 +255,9 @@ defmodule Pleroma.FollowingRelationship do ? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?) """, - activity.actor, + user_actor.ap_id, ^user.domain_blocks, - activity.actor, + user_actor.ap_id, ^User.binary_id(user.id), ^accept_state_code() ) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 368e609d2..710b19866 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -137,7 +137,7 @@ defmodule Pleroma.Notification do blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user) query - |> where([n, a], a.actor not in ^blocked_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocked_ap_ids) |> FollowingRelationship.keep_following_or_not_domain_blocked(user) end @@ -148,7 +148,7 @@ defmodule Pleroma.Notification do blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block]) query - |> where([n, a], a.actor not in ^blocker_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocker_ap_ids) end end @@ -161,7 +161,7 @@ defmodule Pleroma.Notification do opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user) query - |> where([n, a], a.actor not in ^notification_muted_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^notification_muted_ap_ids) |> join(:left, [n, a], tm in ThreadMute, on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data), as: :thread_mute -- cgit v1.2.3 From 60c4cb21ea329e7256297242b05b7f1e9d53e314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 18 Mar 2024 13:54:15 +0100 Subject: InstanceView: Update features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 84e9a0d3c..210b46d2c 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -130,7 +130,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "profile_directory" end, "pleroma:get:main/ostatus", - "pleroma:group_actors" + "pleroma:group_actors", + "pleroma:bookmark_folders" ] |> Enum.filter(& &1) end -- cgit v1.2.3 From 1413d2e517e51a71c71d6365f93ea94fabc61593 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 18 Mar 2024 15:42:15 -0400 Subject: Remove vestiges of old Postgres support --- lib/pleroma/application.ex | 23 +---------------------- lib/pleroma/search/database_search.ex | 9 +-------- 2 files changed, 2 insertions(+), 30 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index de668052f..f2b234022 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -119,28 +119,7 @@ defmodule Pleroma.Application do max_restarts = Application.get_env(:pleroma, __MODULE__)[:max_restarts] opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts] - result = Supervisor.start_link(children, opts) - - set_postgres_server_version() - - result - end - - defp set_postgres_server_version do - version = - with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"), - {num, _} <- Float.parse(version) do - num - else - e -> - Logger.warning( - "Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6" - ) - - 9.6 - end - - :persistent_term.put({Pleroma.Repo, :postgres_version}, version) + Supervisor.start_link(children, opts) end def load_custom_modules do diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex index c6311e0c7..31bfc7e33 100644 --- a/lib/pleroma/search/database_search.ex +++ b/lib/pleroma/search/database_search.ex @@ -23,19 +23,12 @@ defmodule Pleroma.Search.DatabaseSearch do offset = Keyword.get(options, :offset, 0) author = Keyword.get(options, :author) - search_function = - if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do - :websearch - else - :plain - end - try do Activity |> Activity.with_preloaded_object() |> Activity.restrict_deactivated_users() |> restrict_public(user) - |> query_with(index_type, search_query, search_function) + |> query_with(index_type, search_query, :websearch) |> maybe_restrict_local(user) |> maybe_restrict_author(author) |> maybe_restrict_blocked(user) -- cgit v1.2.3 From 291d531e4cbdb5b63edb5b43914d82dafe356907 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 29 Jan 2024 10:18:11 -0500 Subject: Unify notification push and streaming events for both local and federated activities This also removes generation of notifications for blocked/filtered/muted users and threads. --- lib/pleroma/notification.ex | 45 +++++++++++++--------------- lib/pleroma/web/activity_pub/activity_pub.ex | 3 +- lib/pleroma/web/activity_pub/side_effects.ex | 39 ++++++++++++++++-------- 3 files changed, 48 insertions(+), 39 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 710b19866..654c9c98d 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -361,36 +361,32 @@ defmodule Pleroma.Notification do end end - @spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []} - def create_notifications(activity, options \\ []) + @spec create_notifications(Activity.t()) :: {:ok, [Notification.t()] | []} + def create_notifications(activity) - def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do + def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do object = Object.normalize(activity, fetch: false) if object && object.data["type"] == "Answer" do {:ok, []} else - do_create_notifications(activity, options) + do_create_notifications(activity) end end - def create_notifications(%Activity{data: %{"type" => type}} = activity, options) + def create_notifications(%Activity{data: %{"type" => type}} = activity) when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update"] do - do_create_notifications(activity, options) + do_create_notifications(activity) end - def create_notifications(_, _), do: {:ok, []} + def create_notifications(_), do: {:ok, []} - defp do_create_notifications(%Activity{} = activity, options) do - do_send = Keyword.get(options, :do_send, true) - - {enabled_receivers, disabled_receivers} = get_notified_from_activity(activity) - potential_receivers = enabled_receivers ++ disabled_receivers + defp do_create_notifications(%Activity{} = activity) do + enabled_receivers = get_notified_from_activity(activity) notifications = - Enum.map(potential_receivers, fn user -> - do_send = do_send && user in enabled_receivers - create_notification(activity, user, do_send: do_send) + Enum.map(enabled_receivers, fn user -> + create_notification(activity, user) end) |> Enum.reject(&is_nil/1) @@ -450,7 +446,6 @@ defmodule Pleroma.Notification do # TODO move to sql, too. def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do - do_send = Keyword.get(opts, :do_send, true) type = Keyword.get(opts, :type, type_from_activity(activity)) unless skip?(activity, user, opts) do @@ -465,11 +460,6 @@ defmodule Pleroma.Notification do |> Marker.multi_set_last_read_id(user, "notifications") |> Repo.transaction() - if do_send do - Streamer.stream(["user", "user:notification"], notification) - Push.send(notification) - end - notification end end @@ -527,10 +517,7 @@ defmodule Pleroma.Notification do |> exclude_relationship_restricted_ap_ids(activity) |> exclude_thread_muter_ap_ids(activity) - notification_enabled_users = - Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end) - - {notification_enabled_users, potential_receivers -- notification_enabled_users} + Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end) end def get_notified_from_activity(_, _local_only), do: {[], []} @@ -748,4 +735,12 @@ defmodule Pleroma.Notification do ) |> Repo.update_all(set: [seen: true]) end + + @spec send(list(Notification.t())) :: :ok + def send(notifications) do + Enum.each(notifications, fn notification -> + Streamer.stream(["user", "user:notification"], notification) + Push.send(notification) + end) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 2017c696d..a6ec67025 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -202,7 +202,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def notify_and_stream(activity) do - Notification.create_notifications(activity) + {:ok, notifications} = Notification.create_notifications(activity) + Notification.send(notifications) original_activity = case activity do diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 5cb8a9700..982927e16 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -21,7 +21,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.Push alias Pleroma.Web.Streamer alias Pleroma.Workers.PollWorker @@ -125,7 +124,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do nil end - {:ok, notifications} = Notification.create_notifications(object, do_send: false) + {:ok, notifications} = Notification.create_notifications(object) meta = meta @@ -184,7 +183,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do liked_object = Object.get_by_ap_id(object.data["object"]) Utils.add_like_to_object(object, liked_object) - Notification.create_notifications(object) + {:ok, notifications} = Notification.create_notifications(object) + + meta = + meta + |> add_notifications(notifications) {:ok, object, meta} end @@ -202,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do def handle(%{data: %{"type" => "Create"}} = activity, meta) do with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta), %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do - {:ok, notifications} = Notification.create_notifications(activity, do_send: false) + {:ok, notifications} = Notification.create_notifications(activity) {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object) {:ok, _user} = ActivityPub.update_last_status_at_if_public(user, object) @@ -258,11 +261,20 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Utils.add_announce_to_object(object, announced_object) - if !User.internal?(user) do - Notification.create_notifications(object) + notifications = + if !User.is_internal_user?(user) do + {:ok, notifications} = Notification.create_notifications(object) - ap_streamer().stream_out(object) - end + ap_streamer().stream_out(object) + + notifications + else + [] + end + + meta = + meta + |> add_notifications(notifications) {:ok, object, meta} end @@ -283,7 +295,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do reacted_object = Object.get_by_ap_id(object.data["object"]) Utils.add_emoji_reaction_to_object(object, reacted_object) - Notification.create_notifications(object) + {:ok, notifications} = Notification.create_notifications(object) + + meta = + meta + |> add_notifications(notifications) {:ok, object, meta} end @@ -587,10 +603,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do defp send_notifications(meta) do Keyword.get(meta, :notifications, []) - |> Enum.each(fn notification -> - Streamer.stream(["user", "user:notification"], notification) - Push.send(notification) - end) + |> Notification.send() meta end -- cgit v1.2.3 From c25fda34e7560d0d8eb5fdbfb444b641c4c4bf53 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 30 Jan 2024 10:41:52 -0500 Subject: Skip generating notifications for internal users --- lib/pleroma/notification.ex | 7 +++++++ lib/pleroma/web/activity_pub/side_effects.ex | 11 ++--------- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 654c9c98d..a80279fa6 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -630,6 +630,7 @@ defmodule Pleroma.Notification do def skip?(%Activity{} = activity, %User{} = user, opts) do [ :self, + :internal, :invisible, :block_from_strangers, :recently_followed, @@ -649,6 +650,12 @@ defmodule Pleroma.Notification do end end + def skip?(:internal, %Activity{} = activity, _user, _opts) do + actor = activity.data["actor"] + user = User.get_cached_by_ap_id(actor) + User.internal?(user) + end + def skip?(:invisible, %Activity{} = activity, _user, _opts) do actor = activity.data["actor"] user = User.get_cached_by_ap_id(actor) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 982927e16..7ae16eb57 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -261,16 +261,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do Utils.add_announce_to_object(object, announced_object) - notifications = - if !User.is_internal_user?(user) do - {:ok, notifications} = Notification.create_notifications(object) - - ap_streamer().stream_out(object) + {:ok, notifications} = Notification.create_notifications(object) - notifications - else - [] - end + if !User.internal?(user), do: ap_streamer().stream_out(object) meta = meta -- cgit v1.2.3 From 741f22bfe0129cdec07adb954856f3018db79c97 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 8 Mar 2024 10:32:15 -0500 Subject: MediaHelper: cache failed URLs for 15 minutes to prevent excessive retries --- lib/pleroma/application.ex | 1 + lib/pleroma/helpers/media_helper.ex | 49 +++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index f2b234022..75154f94c 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -156,6 +156,7 @@ defmodule Pleroma.Application do build_cachex("web_resp", limit: 2500), build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10), build_cachex("failed_proxy_url", limit: 2500), + build_cachex("failed_media_helper_url", default_ttl: :timer.minutes(15), limit: 2_500), build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000), build_cachex("chat_message_id_idempotency_key", expiration: chat_message_id_idempotency_key_expiration(), diff --git a/lib/pleroma/helpers/media_helper.ex b/lib/pleroma/helpers/media_helper.ex index 7864296fa..e44114d9d 100644 --- a/lib/pleroma/helpers/media_helper.ex +++ b/lib/pleroma/helpers/media_helper.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Helpers.MediaHelper do require Logger + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + def missing_dependencies do Enum.reduce([ffmpeg: "ffmpeg"], [], fn {sym, executable}, acc -> if Pleroma.Utils.command_available?(executable) do @@ -43,29 +45,40 @@ defmodule Pleroma.Helpers.MediaHelper do @spec video_framegrab(String.t()) :: {:ok, binary()} | {:error, any()} def video_framegrab(url) do with executable when is_binary(executable) <- System.find_executable("ffmpeg"), + false <- @cachex.exists?(:failed_media_helper_cache, url), {:ok, env} <- HTTP.get(url, [], pool: :media), {:ok, pid} <- StringIO.open(env.body) do body_stream = IO.binstream(pid, 1) - result = - Exile.stream!( - [ - executable, - "-i", - "pipe:0", - "-vframes", - "1", - "-f", - "mjpeg", - "pipe:1" - ], - input: body_stream, - ignore_epipe: true, - stderr: :disable - ) - |> Enum.into(<<>>) + task = + Task.async(fn -> + Exile.stream!( + [ + executable, + "-i", + "pipe:0", + "-vframes", + "1", + "-f", + "mjpeg", + "pipe:1" + ], + input: body_stream, + ignore_epipe: true, + stderr: :disable + ) + |> Enum.into(<<>>) + end) + + case Task.yield(task, 5_000) do + nil -> + Task.shutdown(task) + @cachex.put(:failed_media_helper_cache, url, nil) + {:error, {:ffmpeg, :timeout}} - {:ok, result} + result -> + {:ok, result} + end else nil -> {:error, {:ffmpeg, :command_not_found}} {:error, _} = error -> error -- cgit v1.2.3 From 37ec645ff2341879c5baf4ad4abe89af0b8195fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 20 Mar 2024 13:21:45 +0100 Subject: Fix BookmarkFolderView, add test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex index fc6ad59d0..12decb816 100644 --- a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex +++ b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex @@ -13,10 +13,8 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do %{ id: folder.id |> to_string(), name: folder.name, - emoji: get_emoji(folder.emoji), - source: %{ - emoji: folder.emoji - } + emoji: folder.emoji, + emoji_url: get_emoji_url(folder.emoji) } end @@ -24,18 +22,18 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders)) end - defp get_emoji(nil) do + defp get_emoji_url(nil) do nil end - defp get_emoji(emoji) do + defp get_emoji_url(emoji) do if Emoji.unicode?(emoji) do - emoji + nil else emoji = Emoji.get(emoji) if emoji != nil do - Endpoint.url() |> URI.merge(emoji.relative_url) |> to_string() + Endpoint.url() |> URI.merge(emoji.file) |> to_string() else nil end -- cgit v1.2.3 From ccc3ac241f5b7c88b36efe60a4f9e5d791d2d49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 6 Apr 2024 10:54:59 +0200 Subject: Add hint to rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/rule.ex | 3 ++- lib/pleroma/web/admin_api/views/rule_view.ex | 3 ++- lib/pleroma/web/api_spec/operations/admin/report_operation.ex | 3 ++- lib/pleroma/web/api_spec/operations/admin/rule_operation.ex | 9 ++++++--- lib/pleroma/web/api_spec/operations/instance_operation.ex | 3 ++- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 ++- 6 files changed, 16 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex index c8e3470c7..3ba413214 100644 --- a/lib/pleroma/rule.ex +++ b/lib/pleroma/rule.ex @@ -14,13 +14,14 @@ defmodule Pleroma.Rule do schema "rules" do field(:priority, :integer, default: 0) field(:text, :string) + field(:hint, :string) timestamps() end def changeset(%Rule{} = rule, params \\ %{}) do rule - |> cast(params, [:priority, :text]) + |> cast(params, [:priority, :text, :hint]) |> validate_required([:text]) end diff --git a/lib/pleroma/web/admin_api/views/rule_view.ex b/lib/pleroma/web/admin_api/views/rule_view.ex index abfdd593f..606443f05 100644 --- a/lib/pleroma/web/admin_api/views/rule_view.ex +++ b/lib/pleroma/web/admin_api/views/rule_view.ex @@ -15,7 +15,8 @@ defmodule Pleroma.Web.AdminAPI.RuleView do %{ id: to_string(rule.id), priority: rule.priority, - text: rule.text + text: rule.text, + hint: rule.hint } end end diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index 971231f4d..25a604beb 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -182,7 +182,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do type: :object, properties: %{ id: %Schema{type: :string}, - text: %Schema{type: :string} + text: %Schema{type: :string}, + hint: %Schema{type: :string, nullable: true} } } } diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex index 2360880e4..c3a3ecc7c 100644 --- a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -84,7 +84,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do required: [:text], properties: %{ priority: %Schema{type: :integer}, - text: %Schema{type: :string} + text: %Schema{type: :string}, + hint: %Schema{type: :string} } } end @@ -94,7 +95,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do type: :object, properties: %{ priority: %Schema{type: :integer}, - text: %Schema{type: :string} + text: %Schema{type: :string}, + hint: %Schema{type: :string} } } end @@ -105,7 +107,8 @@ defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do properties: %{ id: %Schema{type: :string}, priority: %Schema{type: :integer}, - text: %Schema{type: :string} + text: %Schema{type: :string}, + hint: %Schema{type: :string, nullable: true} } } end diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index faf79c2ea..4cd457ae7 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -364,7 +364,8 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do type: :object, properties: %{ id: %Schema{type: :string}, - text: %Schema{type: :string} + text: %Schema{type: :string}, + hint: %Schema{type: :string} } } } diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 6a3d445c5..d00f1bd9e 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -85,7 +85,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do def render("rule.json", %{rule: rule}) do %{ id: to_string(rule.id), - text: rule.text + text: rule.text, + hint: rule.hint || "" } end -- cgit v1.2.3 From 4f5c4d79c4534f0d9976543f718bea3edea843af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 11 Apr 2024 16:50:06 +0200 Subject: FEP-2c59, add "webfinger" to user actor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/activity_pub/views/user_view.ex | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 24ee683ae..937e4fd67 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -67,8 +67,13 @@ defmodule Pleroma.Web.ActivityPub.UserView do def render("user.json", %{user: %User{nickname: nil} = user}), do: render("service.json", %{user: user}) - def render("user.json", %{user: %User{nickname: "internal." <> _} = user}), - do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname) + def render("user.json", %{user: %User{nickname: "internal." <> _} = user}) do + render("service.json", %{user: user}) + |> Map.merge(%{ + "preferredUsername" => user.nickname, + "webfinger" => "acct:#{User.full_nickname(user)}" + }) + end def render("user.json", %{user: user}) do {:ok, _, public_key} = Keys.keys_from_pem(user.keys) @@ -121,7 +126,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do "discoverable" => user.is_discoverable, "capabilities" => capabilities, "alsoKnownAs" => user.also_known_as, - "vcard:bday" => birthday + "vcard:bday" => birthday, + "webfinger" => "acct:#{User.full_nickname(user)}" } |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) -- cgit v1.2.3 From a299ddb10e5b682194710d97eed541277c9d22b6 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 17 Apr 2024 07:37:41 +0200 Subject: =?UTF-8?q?ReceiverWorker:=20Make=20sure=20non-{:ok,=20=5F}=20is?= =?UTF-8?q?=20returned=20as=20{:error,=20=E2=80=A6}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise an error like `{:signature, {:error, {:error, :not_found}}}` ends up considered a success. --- lib/pleroma/workers/receiver_worker.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index 1dddd8d2e..8b2052c23 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -52,7 +52,8 @@ defmodule Pleroma.Workers.ReceiverWorker do {:error, {:reject, reason}} -> {:cancel, reason} {:signature, false} -> {:cancel, :invalid_signature} {:error, {:error, reason = "Object has been deleted"}} -> {:cancel, reason} - e -> e + {:error, _} = e -> e + e -> {:error, e} end end end -- cgit v1.2.3 From 6f6bede9003fc99b24356f06aebfc5dabe54c46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 4 Apr 2024 14:26:55 +0200 Subject: Include image description in status media cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/schemas/status.ex | 4 ++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 1 + 2 files changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index a4052803b..6e537b5da 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -58,6 +58,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do format: :uri, description: "Preview thumbnail" }, + image_description: %Schema{ + type: :string, + description: "Alternate text that describes what is in the thumbnail" + }, title: %Schema{type: :string, description: "Title of linked resource"}, description: %Schema{type: :string, description: "Description of preview"} } diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e464f60dc..5a24d73c7 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -589,6 +589,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do provider_url: page_url_data.scheme <> "://" <> page_url_data.host, url: page_url, image: image_url, + image_description: rich_media["image:alt"] || "", title: rich_media["title"] || "", description: rich_media["description"] || "", pleroma: %{ -- cgit v1.2.3 From dd031848112ef812bdb7af9d485360cc4f0ba13a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 11:54:45 -0400 Subject: Strip actor from objects before federating --- lib/pleroma/constants.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index d814b4931..4d2cd0b62 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Constants do const(object_internal_fields, do: [ + "actor", "reactions", "reaction_count", "likes", -- cgit v1.2.3 From 3cad57bf48180ee5f308ad491c21bcf231a7ba69 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 17:23:41 -0400 Subject: Add configuration[statuses][characters_reserved_per_url] to /api/v2/instance Fixes #3250 --- lib/pleroma/web/api_spec/operations/instance_operation.ex | 5 +++++ lib/pleroma/web/mastodon_api/views/instance_view.ex | 1 + 2 files changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 708b74b12..57aec83a2 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -285,6 +285,11 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do type: :object, description: "A map with poll limits for local statuses", properties: %{ + characters_reserved_per_url: %Schema{ + type: :integer, + description: + "Each URL in a status will be assumed to be exactly this many characters." + }, max_characters: %Schema{ type: :integer, description: "Posts character limit (CW/Subject included in the counter)" diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 210b46d2c..337b2cc83 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -213,6 +213,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do defp configuration2 do configuration() + |> put_in([:statuses, :characters_reserved_per_url], 0) |> Map.merge(%{ urls: %{ streaming: Pleroma.Web.Endpoint.websocket_url(), -- cgit v1.2.3 From b979389958e2d96212cf54ad917d55da86524e30 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 17:45:02 -0400 Subject: Add configuration[accounts][max_pinned_statuses] to /api/v2/instance Also add the absent max_featured_tags to the api spec for /api/v2/instance --- lib/pleroma/web/api_spec/operations/instance_operation.ex | 13 +++++++++++++ lib/pleroma/web/mastodon_api/views/instance_view.ex | 1 + 2 files changed, 14 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 57aec83a2..bc37cae75 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -272,6 +272,19 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do type: :object, description: "Instance configuration", properties: %{ + accounts: %Schema{ + type: :object, + properties: %{ + max_featured_tags: %Schema{ + type: :integer, + description: "The maximum number of featured tags allowed for each account." + }, + max_pinned_statuses: %Schema{ + type: :integer, + description: "The maximum number of pinned statuses for each account." + } + } + }, urls: %Schema{ type: :object, properties: %{ diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 337b2cc83..890dd3977 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -213,6 +213,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do defp configuration2 do configuration() + |> put_in([:accounts, :max_pinned_statuses], Config.get([:instance, :max_pinned_statuses], 0)) |> put_in([:statuses, :characters_reserved_per_url], 0) |> Map.merge(%{ urls: %{ -- cgit v1.2.3 From 06c26bf9c964986b9018ca843be94767f41636a3 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 17:46:05 -0400 Subject: Add the absent max_featured_tags to the api spec for /api/v1/instance --- lib/pleroma/web/api_spec/operations/instance_operation.ex | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index bc37cae75..b6c411c07 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -50,6 +50,15 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do %Schema{ type: :object, properties: %{ + accounts: %Schema{ + type: :object, + properties: %{ + max_featured_tags: %Schema{ + type: :integer, + description: "The maximum number of featured tags allowed for each account." + } + } + }, uri: %Schema{type: :string, description: "The domain name of the instance"}, title: %Schema{type: :string, description: "The title of the website"}, description: %Schema{ -- cgit v1.2.3 From 750fb25f48d1d90157100acee5d2ee019d0bd9be Mon Sep 17 00:00:00 2001 From: feld Date: Tue, 7 May 2024 23:20:38 +0000 Subject: Revert "Merge branch 'pleroma-card-image-description' into 'develop'" This reverts merge request !4101 --- lib/pleroma/web/api_spec/schemas/status.ex | 4 ---- lib/pleroma/web/mastodon_api/views/status_view.ex | 1 - 2 files changed, 5 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 6e537b5da..a4052803b 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -58,10 +58,6 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do format: :uri, description: "Preview thumbnail" }, - image_description: %Schema{ - type: :string, - description: "Alternate text that describes what is in the thumbnail" - }, title: %Schema{type: :string, description: "Title of linked resource"}, description: %Schema{type: :string, description: "Description of preview"} } diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 5a24d73c7..e464f60dc 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -589,7 +589,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do provider_url: page_url_data.scheme <> "://" <> page_url_data.host, url: page_url, image: image_url, - image_description: rich_media["image:alt"] || "", title: rich_media["title"] || "", description: rich_media["description"] || "", pleroma: %{ -- cgit v1.2.3 From ede414094f7b196d3ff129b8a23ba461ef80d29f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 11 Feb 2024 16:11:52 -0500 Subject: RichMedia refactor Rich Media parsing was previously handled on-demand with a 2 second HTTP request timeout and retained only in Cachex. Every time a Pleroma instance is restarted it will have to request and parse the data for each status with a URL detected. When fetching a batch of statuses they were processed in parallel to attempt to keep the maximum latency at 2 seconds, but often resulted in a timeline appearing to hang during loading due to a URL that could not be successfully reached. URLs which had images links that expire (Amazon AWS) were parsed and inserted with a TTL to ensure the image link would not break. Rich Media data is now cached in the database and fetched asynchronously. Cachex is used as a read-through cache. When the data becomes available we stream an update to the clients. If the result is returned quickly the experience is almost seamless. Activities were already processed for their Rich Media data during ingestion to warm the cache, so users should not normally encounter the asynchronous loading of the Rich Media data. Implementation notes: - The async worker is a Task with a globally unique process name to prevent duplicate processing of the same URL - The Task will attempt to fetch the data 3 times with increasing sleep time between attempts - The HTTP request obeys the default HTTP request timeout value instead of 2 seconds - URLs that cannot be successfully parsed due to an unexpected error receives a negative cache entry for 15 minutes - URLs that fail with an expected error will receive a negative cache with no TTL - Activities that have no detected URLs insert a nil value in the Cachex :scrubber_cache so we do not repeat parsing the object content with Floki every time the activity is rendered - Expiring image URLs are handled with an Oban job - There is no automatic cleanup of the Rich Media data in the database, but it is safe to delete at any time - The post draft/preview feature makes the URL processing synchronous so the rendered post preview will have an accurate rendering Overall performance of timelines and creating new posts which contain URLs is greatly improved. --- lib/pleroma/html.ex | 20 ++- lib/pleroma/web/activity_pub/activity_pub.ex | 6 +- lib/pleroma/web/activity_pub/side_effects.ex | 4 +- .../mastodon_api/controllers/status_controller.ex | 7 +- lib/pleroma/web/mastodon_api/views/status_view.ex | 26 ++-- .../views/chat/message_reference_view.ex | 13 +- lib/pleroma/web/rich_media/backfill.ex | 101 +++++++++++++ lib/pleroma/web/rich_media/card.ex | 157 +++++++++++++++++++++ lib/pleroma/web/rich_media/helpers.ex | 70 ++------- lib/pleroma/web/rich_media/parser.ex | 114 +-------------- lib/pleroma/web/rich_media/parser/ttl.ex | 12 ++ .../web/rich_media/parser/ttl/aws_signed_url.ex | 2 +- .../workers/rich_media_expiration_worker.ex | 15 ++ 13 files changed, 336 insertions(+), 211 deletions(-) create mode 100644 lib/pleroma/web/rich_media/backfill.ex create mode 100644 lib/pleroma/web/rich_media/card.ex create mode 100644 lib/pleroma/workers/rich_media_expiration_worker.ex (limited to 'lib') diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 84ff2f129..4de7cbb76 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -65,20 +65,16 @@ defmodule Pleroma.HTML do end end - @spec extract_first_external_url_from_object(Pleroma.Object.t()) :: - {:ok, String.t()} | {:error, :no_content} + @spec extract_first_external_url_from_object(Pleroma.Object.t()) :: String.t() | nil def extract_first_external_url_from_object(%{data: %{"content" => content}}) when is_binary(content) do - url = - content - |> Floki.parse_fragment!() - |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") - |> Enum.take(1) - |> Floki.attribute("href") - |> Enum.at(0) - - {:ok, url} + content + |> Floki.parse_fragment!() + |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") + |> Enum.take(1) + |> Floki.attribute("href") + |> Enum.at(0) end - def extract_first_external_url_from_object(_), do: {:error, :no_content} + def extract_first_external_url_from_object(_), do: nil end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 2017c696d..a1fccc705 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -147,9 +147,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do # Splice in the child object if we have one. activity = Maps.put_if_present(activity, :object, object) - ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn -> - Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) - end) + Pleroma.Web.RichMedia.Card.get_by_activity(activity) # Add local posts to search index if local, do: Pleroma.Search.add_to_index(activity) @@ -177,7 +175,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do id: "pleroma:fakeid" } - Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + Pleroma.Web.RichMedia.Card.get_by_activity(activity) {:ok, activity} {:remote_limit_pass, _} -> diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 5cb8a9700..7421b8ed8 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -227,9 +227,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do end end - ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn -> - Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) - end) + Pleroma.Web.RichMedia.Card.get_by_activity(activity) Pleroma.Search.add_to_index(Map.put(activity, :object, object)) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 4f6de8a00..b7dc00a44 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -25,6 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter + alias Pleroma.Web.RichMedia.Card plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) @@ -480,9 +481,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do _ ) do with %Activity{} = activity <- Activity.get_by_id(status_id), - true <- Visibility.visible_for_user?(activity, user) do - data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - render(conn, "card.json", data) + true <- Visibility.visible_for_user?(activity, user), + %Card{} = card_data <- Card.get_by_activity(activity) do + render(conn, "card.json", card_data) else _ -> render_error(conn, :not_found, "Record not found") end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index e464f60dc..77af69eef 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 + alias Pleroma.Web.RichMedia.Card import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2] @@ -29,9 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # pagination is restricted to 40 activities at a time defp fetch_rich_media_for_activities(activities) do Enum.each(activities, fn activity -> - spawn(fn -> - Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) - end) + spawn(fn -> Card.get_by_activity(activity) end) end) end @@ -113,9 +112,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list activities = Enum.filter(opts.activities, & &1) - # Start fetching rich media before doing anything else, so that later calls to get the cards - # only block for timeout in the worst case, as opposed to - # length(activities_with_links) * timeout + # Start prefetching rich media before doing anything else fetch_rich_media_for_activities(activities) replied_to_activities = get_replied_to_activities(activities) quoted_activities = get_quoted_activities(activities) @@ -364,7 +361,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do summary = object.data["summary"] || "" - card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)) + card = + case Card.get_by_activity(activity) do + %Card{} = result -> render("card.json", result) + _ -> nil + end url = if user.local do @@ -567,15 +568,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } end - def render("card.json", %{rich_media: rich_media, page_url: page_url}) do - page_url_data = URI.parse(page_url) - - page_url_data = - if is_binary(rich_media["url"]) do - URI.merge(page_url_data, URI.parse(rich_media["url"])) - else - page_url_data - end + def render("card.json", %Card{fields: rich_media}) do + page_url_data = URI.parse(rich_media["url"]) page_url = page_url_data |> to_string diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex index 241bf0010..a1c88d075 100644 --- a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex +++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do alias Pleroma.User alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.RichMedia.Card @cachex Pleroma.Config.get([:cachex, :provider], Cachex) @@ -23,6 +24,12 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do } } ) do + card = + case Card.get_by_object(object) do + %Card{} = card_data -> StatusView.render("card.json", card_data) + _ -> nil + end + %{ id: id |> to_string(), content: chat_message["content"], @@ -34,11 +41,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do chat_message["attachment"] && StatusView.render("attachment.json", attachment: chat_message["attachment"]), unread: unread, - card: - StatusView.render( - "card.json", - Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object) - ) + card: card } |> put_idempotency_key() end diff --git a/lib/pleroma/web/rich_media/backfill.ex b/lib/pleroma/web/rich_media/backfill.ex new file mode 100644 index 000000000..112028901 --- /dev/null +++ b/lib/pleroma/web/rich_media/backfill.ex @@ -0,0 +1,101 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Backfill.Task do + alias Pleroma.Web.RichMedia.Backfill + + def run(args) do + Task.Supervisor.start_child(Pleroma.TaskSupervisor, Backfill, :run, [args], + name: {:global, {:rich_media, args.url_hash}} + ) + end +end + +defmodule Pleroma.Web.RichMedia.Backfill do + alias Pleroma.Web.RichMedia.Card + alias Pleroma.Web.RichMedia.Parser + alias Pleroma.Web.RichMedia.Parser.TTL + alias Pleroma.Workers.RichMediaExpirationWorker + + require Logger + + @backfiller Pleroma.Config.get([__MODULE__, :provider], Pleroma.Web.RichMedia.Backfill.Task) + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @max_attempts 3 + @retry 5_000 + + def start(%{url: url} = args) when is_binary(url) do + url_hash = Card.url_to_hash(url) + + args = + args + |> Map.put(:attempt, 1) + |> Map.put(:url_hash, url_hash) + + @backfiller.run(args) + end + + def run(%{url: url, url_hash: url_hash, attempt: attempt} = args) + when attempt <= @max_attempts do + case Parser.parse(url) do + {:ok, fields} -> + {:ok, card} = Card.create(url, fields) + + maybe_schedule_expiration(url, fields) + + if Map.has_key?(args, :activity_id) do + stream_update(args) + end + + warm_cache(url_hash, card) + + {:error, {:invalid_metadata, fields}} -> + Logger.debug("Rich media incomplete or invalid metadata for #{url}: #{inspect(fields)}") + negative_cache(url_hash) + + {:error, :body_too_large} -> + Logger.error("Rich media error for #{url}: :body_too_large") + negative_cache(url_hash) + + {:error, {:content_type, type}} -> + Logger.debug("Rich media error for #{url}: :content_type is #{type}") + negative_cache(url_hash) + + e -> + Logger.debug("Rich media error for #{url}: #{inspect(e)}") + + :timer.sleep(@retry * attempt) + + run(%{args | attempt: attempt + 1}) + end + end + + def run(%{url: url, url_hash: url_hash}) do + Logger.debug("Rich media failure for #{url}") + + negative_cache(url_hash, :timer.minutes(15)) + end + + defp maybe_schedule_expiration(url, fields) do + case TTL.get_from_image(fields, url) do + ttl when is_number(ttl) -> + timestamp = DateTime.from_unix!(ttl) + + RichMediaExpirationWorker.new(%{"url" => url}, scheduled_at: timestamp) + |> Oban.insert() + + _ -> + :ok + end + end + + defp stream_update(%{activity_id: activity_id}) do + Pleroma.Activity.get_by_id(activity_id) + |> Pleroma.Activity.normalize() + |> Pleroma.Web.ActivityPub.ActivityPub.stream_out() + end + + defp warm_cache(key, val), do: @cachex.put(:rich_media_cache, key, val) + defp negative_cache(key, ttl \\ nil), do: @cachex.put(:rich_media_cache, key, nil, ttl: ttl) +end diff --git a/lib/pleroma/web/rich_media/card.ex b/lib/pleroma/web/rich_media/card.ex new file mode 100644 index 000000000..2d36f2b62 --- /dev/null +++ b/lib/pleroma/web/rich_media/card.ex @@ -0,0 +1,157 @@ +defmodule Pleroma.Web.RichMedia.Card do + use Ecto.Schema + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.Activity + alias Pleroma.HTML + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.Web.RichMedia.Backfill + alias Pleroma.Web.RichMedia.Parser + + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) + + @type t :: %__MODULE__{} + + schema "rich_media_card" do + field(:url_hash, :binary) + field(:fields, :map) + + timestamps() + end + + @doc false + def changeset(card, attrs) do + card + |> cast(attrs, [:url_hash, :fields]) + |> validate_required([:url_hash, :fields]) + |> unique_constraint(:url_hash) + end + + @spec create(String.t(), map()) :: {:ok, t()} + def create(url, fields) do + url_hash = url_to_hash(url) + + fields = Map.put_new(fields, "url", url) + + %__MODULE__{} + |> changeset(%{url_hash: url_hash, fields: fields}) + |> Repo.insert(on_conflict: {:replace, [:fields]}, conflict_target: :url_hash) + end + + @spec delete(String.t()) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} | :ok + def delete(url) do + url_hash = url_to_hash(url) + @cachex.del(:rich_media_cache, url_hash) + + case get_by_url(url) do + %__MODULE{} = card -> Repo.delete(card) + nil -> :ok + end + end + + @spec get_by_url(String.t() | nil) :: t() | nil | :error + def get_by_url(url) when is_binary(url) do + if @config_impl.get([:rich_media, :enabled]) do + url_hash = url_to_hash(url) + + @cachex.fetch!(:rich_media_cache, url_hash, fn _ -> + result = + __MODULE__ + |> where(url_hash: ^url_hash) + |> Repo.one() + + case result do + %__MODULE__{} = card -> {:commit, card} + _ -> {:ignore, nil} + end + end) + else + :error + end + end + + def get_by_url(nil), do: nil + + @spec get_or_backfill_by_url(String.t(), map()) :: t() | nil + def get_or_backfill_by_url(url, backfill_opts \\ %{}) do + case get_by_url(url) do + %__MODULE__{} = card -> + card + + nil -> + backfill_opts = Map.put(backfill_opts, :url, url) + + Backfill.start(backfill_opts) + + nil + + :error -> + nil + end + end + + @spec get_by_object(Object.t()) :: t() | nil | :error + def get_by_object(object) do + case HTML.extract_first_external_url_from_object(object) do + nil -> nil + url -> get_or_backfill_by_url(url) + end + end + + @spec get_by_activity(Activity.t()) :: t() | nil | :error + # Fake/Draft activity + def get_by_activity(%Activity{id: "pleroma:fakeid"} = activity) do + with %Object{} = object <- Object.normalize(activity, fetch: false), + url when not is_nil(url) <- HTML.extract_first_external_url_from_object(object) do + case get_by_url(url) do + # Cache hit + %__MODULE__{} = card -> + card + + # Cache miss, but fetch for rendering the Draft + _ -> + with {:ok, fields} <- Parser.parse(url), + {:ok, card} <- create(url, fields) do + card + else + _ -> nil + end + end + else + _ -> + nil + end + end + + def get_by_activity(activity) do + with %Object{} = object <- Object.normalize(activity, fetch: false), + {_, nil} <- {:cached, get_cached_url(object, activity.id)} do + nil + else + {:cached, url} -> + get_or_backfill_by_url(url, %{activity_id: activity.id}) + + _ -> + :error + end + end + + @spec url_to_hash(String.t()) :: String.t() + def url_to_hash(url) do + :crypto.hash(:sha256, url) |> Base.encode16(case: :lower) + end + + defp get_cached_url(object, activity_id) do + key = "URL|#{activity_id}" + + @cachex.fetch!(:scrubber_cache, key, fn _ -> + url = HTML.extract_first_external_url_from_object(object) + Activity.HTML.add_cache_key_for(activity_id, key) + + {:commit, url} + end) + end +end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index a711dc436..00af140ae 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -3,65 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.Helpers do - alias Pleroma.Activity - alias Pleroma.HTML - alias Pleroma.Object - alias Pleroma.Web.RichMedia.Parser - - @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - - @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) - - @options [ - pool: :media, - max_body: 2_000_000, - recv_timeout: 2_000 - ] - - def fetch_data_for_object(object) do - with true <- @config_impl.get([:rich_media, :enabled]), - {:ok, page_url} <- - HTML.extract_first_external_url_from_object(object), - {:ok, rich_media} <- Parser.parse(page_url) do - %{page_url: page_url, rich_media: rich_media} - else - _ -> %{} - end - end - - def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do - with true <- @config_impl.get([:rich_media, :enabled]), - %Object{} = object <- Object.normalize(activity, fetch: false) do - if object.data["fake"] do - fetch_data_for_object(object) - else - key = "URL|#{activity.id}" - - @cachex.fetch!(:scrubber_cache, key, fn _ -> - result = fetch_data_for_object(object) - - cond do - match?(%{page_url: _, rich_media: _}, result) -> - Activity.HTML.add_cache_key_for(activity.id, key) - {:commit, result} - - true -> - {:ignore, %{}} - end - end) - end - else - _ -> %{} - end - end - - def fetch_data_for_activity(_), do: %{} + alias Pleroma.Config def rich_media_get(url) do headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] head_check = - case Pleroma.HTTP.head(url, headers, @options) do + case Pleroma.HTTP.head(url, headers, http_options()) do # If the HEAD request didn't reach the server for whatever reason, # we assume the GET that comes right after won't either {:error, _} = e -> @@ -76,7 +24,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do :ok end - with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options) + with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, http_options()) end defp check_content_type(headers) do @@ -92,12 +40,13 @@ defmodule Pleroma.Web.RichMedia.Helpers do end end - @max_body @options[:max_body] defp check_content_length(headers) do + max_body = Keyword.get(http_options(), :max_body) + case List.keyfind(headers, "content-length", 0) do {_, maybe_content_length} -> case Integer.parse(maybe_content_length) do - {content_length, ""} when content_length <= @max_body -> :ok + {content_length, ""} when content_length <= max_body -> :ok {_, ""} -> {:error, :body_too_large} _ -> :ok end @@ -106,4 +55,11 @@ defmodule Pleroma.Web.RichMedia.Helpers do :ok end end + + defp http_options() do + [ + pool: :media, + max_body: Config.get([:rich_media, :max_body], 2_000_000) + ] + end end diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index a73fbc4b9..37cf29029 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -5,134 +5,28 @@ defmodule Pleroma.Web.RichMedia.Parser do require Logger - @cachex Pleroma.Config.get([:cachex, :provider], Cachex) @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) defp parsers do Pleroma.Config.get([:rich_media, :parsers]) end - def parse(nil), do: {:error, "No URL provided"} + def parse(nil), do: nil @spec parse(String.t()) :: {:ok, map()} | {:error, any()} def parse(url) do with :ok <- validate_page_url(url), - {:ok, data} <- get_cached_or_parse(url), - {:ok, _} <- set_ttl_based_on_image(data, url) do + {:ok, data} <- parse_url(url) do + data = Map.put(data, "url", url) {:ok, data} end end - defp get_cached_or_parse(url) do - case @cachex.fetch(:rich_media_cache, url, fn -> - case parse_url(url) do - {:ok, _} = res -> - {:commit, res} - - {:error, reason} = e -> - # Unfortunately we have to log errors here, instead of doing that - # along with ttl setting at the bottom. Otherwise we can get log spam - # if more than one process was waiting for the rich media card - # while it was generated. Ideally we would set ttl here as well, - # so we don't override it number_of_waiters_on_generation - # times, but one, obviously, can't set ttl for not-yet-created entry - # and Cachex doesn't support returning ttl from the fetch callback. - log_error(url, reason) - {:commit, e} - end - end) do - {action, res} when action in [:commit, :ok] -> - case res do - {:ok, _data} = res -> - res - - {:error, reason} = e -> - if action == :commit, do: set_error_ttl(url, reason) - e - end - - {:error, e} -> - {:error, {:cachex_error, e}} - end - end - - defp set_error_ttl(_url, :body_too_large), do: :ok - defp set_error_ttl(_url, {:content_type, _}), do: :ok - - # The TTL is not set for the errors above, since they are unlikely to change - # with time - - defp set_error_ttl(url, _reason) do - ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) - @cachex.expire(:rich_media_cache, url, ttl) - :ok - end - - defp log_error(url, {:invalid_metadata, data}) do - Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) - end - - defp log_error(url, reason) do - Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) - end - - @doc """ - Set the rich media cache based on the expiration time of image. - - Adopt behaviour `Pleroma.Web.RichMedia.Parser.TTL` - - ## Example - - defmodule MyModule do - @behaviour Pleroma.Web.RichMedia.Parser.TTL - def ttl(data, url) do - image_url = Map.get(data, :image) - # do some parsing in the url and get the ttl of the image - # and return ttl is unix time - parse_ttl_from_url(image_url) - end - end - - Define the module in the config - - config :pleroma, :rich_media, - ttl_setters: [MyModule] - """ - @spec set_ttl_based_on_image(map(), String.t()) :: - {:ok, integer() | :noop} | {:error, :no_key} - def set_ttl_based_on_image(data, url) do - case get_ttl_from_image(data, url) do - ttl when is_number(ttl) -> - ttl = ttl * 1000 - - case @cachex.expire_at(:rich_media_cache, url, ttl) do - {:ok, true} -> {:ok, ttl} - {:ok, false} -> {:error, :no_key} - end - - _ -> - {:ok, :noop} - end - end - - defp get_ttl_from_image(data, url) do - [:rich_media, :ttl_setters] - |> Pleroma.Config.get() - |> Enum.reduce({:ok, nil}, fn - module, {:ok, _ttl} -> - module.ttl(data, url) - - _, error -> - error - end) - end - - def parse_url(url) do + defp parse_url(url) do with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url), {:ok, html} <- Floki.parse_document(html) do html |> maybe_parse() - |> Map.put("url", url) |> clean_parsed_data() |> check_parsed_data() end diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex index b51298bd8..d69bb0d07 100644 --- a/lib/pleroma/web/rich_media/parser/ttl.ex +++ b/lib/pleroma/web/rich_media/parser/ttl.ex @@ -4,4 +4,16 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL do @callback ttl(map(), String.t()) :: integer() | nil + + def get_from_image(data, url) do + [:rich_media, :ttl_setters] + |> Pleroma.Config.get() + |> Enum.reduce({:ok, nil}, fn + module, {:ok, _ttl} -> + module.ttl(data, url) + + _, error -> + error + end) + end end diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex index a0d567c42..22e72e22e 100644 --- a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do @impl true def ttl(data, _url) do - image = Map.get(data, :image) + image = Map.get(data, "image") if aws_signed_url?(image) do image diff --git a/lib/pleroma/workers/rich_media_expiration_worker.ex b/lib/pleroma/workers/rich_media_expiration_worker.ex new file mode 100644 index 000000000..d7ae497a7 --- /dev/null +++ b/lib/pleroma/workers/rich_media_expiration_worker.ex @@ -0,0 +1,15 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.RichMediaExpirationWorker do + alias Pleroma.Web.RichMedia.Card + + use Oban.Worker, + queue: :rich_media_expiration + + @impl Oban.Worker + def perform(%Job{args: %{"url" => url} = _args}) do + Card.delete(url) + end +end -- cgit v1.2.3 From df0734fcbf7adcd98e9bce38cc7aa18345aaf78d Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 11 Feb 2024 16:53:21 -0500 Subject: Increase the :max_body for Rich Media to 5MB Websites are increasingly getting more bloated with tricks like inlining content (e.g., CNN.com) which puts pages at or above 5MB. This value may still be too low. --- lib/pleroma/web/rich_media/helpers.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 00af140ae..2c65d9647 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -59,7 +59,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do defp http_options() do [ pool: :media, - max_body: Config.get([:rich_media, :max_body], 2_000_000) + max_body: Config.get([:rich_media, :max_body], 5_000_000) ] end end -- cgit v1.2.3 From d21aa1a77cbda323ae2e82ea7910e076b6011571 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 18 Feb 2024 22:24:27 -0500 Subject: Respect the TTL returned in OpenGraph tags --- lib/pleroma/web/rich_media/backfill.ex | 4 ++-- lib/pleroma/web/rich_media/parser/ttl.ex | 15 ++++++++------- .../web/rich_media/parser/ttl/aws_signed_url.ex | 2 +- lib/pleroma/web/rich_media/parser/ttl/opengraph.ex | 19 +++++++++++++++++++ 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 lib/pleroma/web/rich_media/parser/ttl/opengraph.ex (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/backfill.ex b/lib/pleroma/web/rich_media/backfill.ex index 112028901..386e2023a 100644 --- a/lib/pleroma/web/rich_media/backfill.ex +++ b/lib/pleroma/web/rich_media/backfill.ex @@ -78,8 +78,8 @@ defmodule Pleroma.Web.RichMedia.Backfill do end defp maybe_schedule_expiration(url, fields) do - case TTL.get_from_image(fields, url) do - ttl when is_number(ttl) -> + case TTL.process(fields, url) do + {:ok, ttl} when is_number(ttl) -> timestamp = DateTime.from_unix!(ttl) RichMediaExpirationWorker.new(%{"url" => url}, scheduled_at: timestamp) diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex index d69bb0d07..7e56375ff 100644 --- a/lib/pleroma/web/rich_media/parser/ttl.ex +++ b/lib/pleroma/web/rich_media/parser/ttl.ex @@ -5,15 +5,16 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL do @callback ttl(map(), String.t()) :: integer() | nil - def get_from_image(data, url) do + @spec process(map(), String.t()) :: {:ok, integer() | nil} + def process(data, url) do [:rich_media, :ttl_setters] |> Pleroma.Config.get() - |> Enum.reduce({:ok, nil}, fn - module, {:ok, _ttl} -> - module.ttl(data, url) - - _, error -> - error + |> Enum.reduce_while({:ok, nil}, fn + module, acc -> + case module.ttl(data, url) do + ttl when is_number(ttl) -> {:halt, {:ok, ttl}} + _ -> {:cont, acc} + end end) end end diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex index 22e72e22e..d6bf50fa5 100644 --- a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex @@ -15,7 +15,7 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do |> format_query_params() |> get_expiration_timestamp() else - {:error, "Not aws signed url #{inspect(image)}"} + nil end end diff --git a/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex b/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex new file mode 100644 index 000000000..fc99244c3 --- /dev/null +++ b/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex @@ -0,0 +1,19 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Parser.TTL.Opengraph do + @behaviour Pleroma.Web.RichMedia.Parser.TTL + + @impl true + def ttl(%{"ttl" => ttl_string}, _url) do + with ttl <- String.to_integer(ttl_string) do + now = DateTime.utc_now() |> DateTime.to_unix() + now + ttl + else + _ -> nil + end + end + + def ttl(_, _), do: nil +end -- cgit v1.2.3 From 5a5a193877dd890db5682d1809e02d4908d11144 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 7 Mar 2024 14:19:03 -0500 Subject: Fix broken Rich Media parsing when the image URL is a relative path --- lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex index d6bf50fa5..948c727e1 100644 --- a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex +++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex @@ -22,7 +22,8 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do defp aws_signed_url?(image) when is_binary(image) and image != "" do %URI{host: host, query: query} = URI.parse(image) - String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") + is_binary(host) and String.contains?(host, "amazonaws.com") and + String.contains?(query, "X-Amz-Expires") end defp aws_signed_url?(_), do: nil -- cgit v1.2.3 From 19002fd6c11760898daf0b5ed648d6ba58d84b97 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 8 May 2024 01:44:58 +0000 Subject: Mastodon API: Remove deprecated GET /api/v1/statuses/:id/card endpoint Removed back in 2019 https://github.com/mastodon/mastodon/pull/11213 --- .../web/mastodon_api/controllers/status_controller.ex | 17 ----------------- lib/pleroma/web/router.ex | 1 - 2 files changed, 18 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index b7dc00a44..83e1bee54 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -25,7 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter - alias Pleroma.Web.RichMedia.Card plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) @@ -39,7 +38,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do when action in [ :index, :show, - :card, :context, :show_history, :show_source @@ -474,21 +472,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do end end - @doc "GET /api/v1/statuses/:id/card" - @deprecated "https://github.com/tootsuite/mastodon/pull/11213" - def card( - %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: status_id}}}} = conn, - _ - ) do - with %Activity{} = activity <- Activity.get_by_id(status_id), - true <- Visibility.visible_for_user?(activity, user), - %Card{} = card_data <- Card.get_by_activity(activity) do - render(conn, "card.json", card_data) - else - _ -> render_error(conn, :not_found, "Record not found") - end - end - @doc "GET /api/v1/statuses/:id/favourited_by" def favourited_by( %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 4fe0cb02f..86d6da883 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -768,7 +768,6 @@ defmodule Pleroma.Web.Router do get("/statuses", StatusController, :index) get("/statuses/:id", StatusController, :show) get("/statuses/:id/context", StatusController, :context) - get("/statuses/:id/card", StatusController, :card) get("/statuses/:id/favourited_by", StatusController, :favourited_by) get("/statuses/:id/reblogged_by", StatusController, :reblogged_by) get("/statuses/:id/history", StatusController, :show_history) -- cgit v1.2.3 From 9b9a32bf74a87047b3c12b468f3351950c334995 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 21:55:39 -0400 Subject: Fix compile warning warning: "else" clauses will never match because all patterns in "with" will always match lib/pleroma/web/rich_media/parser/ttl/opengraph.ex:10 --- lib/pleroma/web/rich_media/parser/ttl/opengraph.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex b/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex index fc99244c3..b06889669 100644 --- a/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex +++ b/lib/pleroma/web/rich_media/parser/ttl/opengraph.ex @@ -6,11 +6,12 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.Opengraph do @behaviour Pleroma.Web.RichMedia.Parser.TTL @impl true - def ttl(%{"ttl" => ttl_string}, _url) do - with ttl <- String.to_integer(ttl_string) do + def ttl(%{"ttl" => ttl_string}, _url) when is_binary(ttl_string) do + try do + ttl = String.to_integer(ttl_string) now = DateTime.utc_now() |> DateTime.to_unix() now + ttl - else + rescue _ -> nil end end -- cgit v1.2.3 From 37c35daba66b3534d8724266118053c11057f2b3 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 22:10:49 -0400 Subject: Credo --- lib/pleroma/web/rich_media/backfill.ex | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/backfill.ex b/lib/pleroma/web/rich_media/backfill.ex index 386e2023a..4ec50e132 100644 --- a/lib/pleroma/web/rich_media/backfill.ex +++ b/lib/pleroma/web/rich_media/backfill.ex @@ -2,16 +2,6 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.RichMedia.Backfill.Task do - alias Pleroma.Web.RichMedia.Backfill - - def run(args) do - Task.Supervisor.start_child(Pleroma.TaskSupervisor, Backfill, :run, [args], - name: {:global, {:rich_media, args.url_hash}} - ) - end -end - defmodule Pleroma.Web.RichMedia.Backfill do alias Pleroma.Web.RichMedia.Card alias Pleroma.Web.RichMedia.Parser @@ -99,3 +89,13 @@ defmodule Pleroma.Web.RichMedia.Backfill do defp warm_cache(key, val), do: @cachex.put(:rich_media_cache, key, val) defp negative_cache(key, ttl \\ nil), do: @cachex.put(:rich_media_cache, key, nil, ttl: ttl) end + +defmodule Pleroma.Web.RichMedia.Backfill.Task do + alias Pleroma.Web.RichMedia.Backfill + + def run(args) do + Task.Supervisor.start_child(Pleroma.TaskSupervisor, Backfill, :run, [args], + name: {:global, {:rich_media, args.url_hash}} + ) + end +end -- cgit v1.2.3 From 9a83301ff8a3cbc187bb869b01ce6dcab63d93a7 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 22:11:19 -0400 Subject: Credo --- lib/pleroma/web/rich_media/helpers.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 2c65d9647..119994458 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -56,7 +56,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do end end - defp http_options() do + defp http_options do [ pool: :media, max_body: Config.get([:rich_media, :max_body], 5_000_000) -- cgit v1.2.3 From 54c2bab25f965e2e3fa8d118bf5135c335eca2c0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 7 May 2024 22:27:18 -0400 Subject: Fix module struct matching --- lib/pleroma/web/rich_media/card.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/rich_media/card.ex b/lib/pleroma/web/rich_media/card.ex index 2d36f2b62..36a1ae44a 100644 --- a/lib/pleroma/web/rich_media/card.ex +++ b/lib/pleroma/web/rich_media/card.ex @@ -47,7 +47,7 @@ defmodule Pleroma.Web.RichMedia.Card do @cachex.del(:rich_media_cache, url_hash) case get_by_url(url) do - %__MODULE{} = card -> Repo.delete(card) + %__MODULE__{} = card -> Repo.delete(card) nil -> :ok end end -- cgit v1.2.3 From 818d9f7b636e8de996f1656eb9bc2f3100889257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 4 Apr 2024 14:26:55 +0200 Subject: Include image description in status media cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/schemas/status.ex | 4 ++++ lib/pleroma/web/mastodon_api/views/status_view.ex | 1 + 2 files changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index a4052803b..6e537b5da 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -58,6 +58,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do format: :uri, description: "Preview thumbnail" }, + image_description: %Schema{ + type: :string, + description: "Alternate text that describes what is in the thumbnail" + }, title: %Schema{type: :string, description: "Title of linked resource"}, description: %Schema{type: :string, description: "Description of preview"} } diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 77af69eef..c945290c1 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -583,6 +583,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do provider_url: page_url_data.scheme <> "://" <> page_url_data.host, url: page_url, image: image_url, + image_description: rich_media["image:alt"] || "", title: rich_media["title"] || "", description: rich_media["description"] || "", pleroma: %{ -- cgit v1.2.3 From 7f8a9329e566140f9f36cecac58b13097b7e0519 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 16 May 2024 16:03:05 -0400 Subject: Startup detection for configured MRF modules that are missing or incorrectly defined --- lib/pleroma/application_requirements.ex | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 819245481..8c0df64fc 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -28,6 +28,7 @@ defmodule Pleroma.ApplicationRequirements do |> check_welcome_message_config!() |> check_rum!() |> check_repo_pool_size!() + |> check_mrfs() |> handle_result() end @@ -234,4 +235,25 @@ defmodule Pleroma.ApplicationRequirements do true end end + + defp check_mrfs(:ok) do + mrfs = Config.get!([:mrf, :policies]) + + missing_mrfs = + Enum.reduce(mrfs, [], fn x, acc -> + if Code.ensure_compiled(x) do + acc + else + acc ++ [x] + end + end) + + if Enum.empty?(missing_mrfs) do + :ok + else + {:error, "The following MRF modules are configured but missing: #{inspect(missing_mrfs)}"} + end + end + + defp check_mrfs(result), do: result end -- cgit v1.2.3 From 9988dc22273a22cd262c84adde184fcab4a4e8ae Mon Sep 17 00:00:00 2001 From: feld Date: Thu, 16 May 2024 23:33:48 +0000 Subject: Revert "Merge branch 'strip-object-actor' into 'develop'" This reverts merge request !4105 --- lib/pleroma/constants.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index ac4bf2ffb..3a5e35301 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Constants do const(object_internal_fields, do: [ - "actor", "reactions", "reaction_count", "likes", -- cgit v1.2.3