logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: 13bc4ba63947ec533a545f8d757ec656ac3e9ab8
parent ed1cfd6f5eee450277f32019dd290f3f48cf258f
Author: nicole mikołajczyk <git@mkljczk.pl>
Date:   Fri, 28 Nov 2025 15:06:50 +0100

Merge remote-tracking branch 'origin/develop' into translation-provider-translatelocally

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>

Diffstat:

M.gitlab-ci.yml44++++++++++++++++++++++++++++----------------
Achangelog.d/authorized_fetch.fix2++
Achangelog.d/ci-artifacts.skip0
Achangelog.d/docs.skip2++
Achangelog.d/endorsements-api.change1+
Achangelog.d/fix-lists-bcc.fix1+
Achangelog.d/instance-view-timeline-access.add1+
Achangelog.d/local-nickname-regex.fix1+
Achangelog.d/notification-cleanup.skip0
Achangelog.d/plaroma.skip2++
Achangelog.d/remote-url.fix1+
Achangelog.d/rss-redirect.change2++
Achangelog.d/status-push-notification.fix1+
Achangelog.d/translation-provider-mozhi.add1+
Mconfig/description.exs18++++++++++++++++--
Mdocs/development/API/differences_in_mastoapi_responses.md8+++++++-
Mdocs/installation/optional/media_graphics_packages.md6+++---
Alib/pleroma/language/translation/mozhi.ex109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/pleroma/notification.ex4+---
Mlib/pleroma/signature.ex2+-
Mlib/pleroma/user.ex15++++++++++++---
Mlib/pleroma/web/activity_pub/publisher.ex27+++++++++++++++++++++------
Mlib/pleroma/web/api_spec/operations/account_operation.ex26++++++++++++++++++++++++--
Mlib/pleroma/web/api_spec/operations/pleroma_account_operation.ex19-------------------
Mlib/pleroma/web/common_api/utils.ex35+++++++++++++++++------------------
Mlib/pleroma/web/feed/user_controller.ex7+++++--
Mlib/pleroma/web/mastodon_api/controllers/account_controller.ex24++++++++++++++++++++----
Mlib/pleroma/web/mastodon_api/views/instance_view.ex26+++++++++++++++++++++++++-
Mlib/pleroma/web/mastodon_api/views/status_view.ex5+++--
Mlib/pleroma/web/pleroma_api/controllers/account_controller.ex25+------------------------
Mlib/pleroma/web/push/subscription.ex2+-
Mlib/pleroma/web/router.ex11+++++++++--
Mtest/pleroma/user_test.exs5+++++
Mtest/pleroma/web/activity_pub/publisher_test.exs27+++++++++++++++++++++++++++
Mtest/pleroma/web/feed/user_controller_test.exs15+++++++++++++++
Mtest/pleroma/web/mastodon_api/controllers/account_controller_test.exs33+++++++++++++++++++++++++++++----
Mtest/pleroma/web/mastodon_api/controllers/instance_controller_test.exs24++++++++++++++++++++++++
Mtest/pleroma/web/pleroma_api/controllers/account_controller_test.exs29-----------------------------
38 files changed, 418 insertions(+), 143 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml @@ -16,9 +16,15 @@ workflow: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == "develop" - if: $CI_COMMIT_BRANCH == "stable" + - if: $CI_PIPELINE_SOURCE == "web" + - if: $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS when: never +# Default artifacts configuration +.default_artifacts: &default_artifacts + expire_in: 30 days + cache: &global_cache_policy key: $CI_JOB_IMAGE-$CI_COMMIT_SHORT_SHA paths: @@ -56,6 +62,7 @@ check-changelog: before_script: '' after_script: '' cache: {} + artifacts: *default_artifacts script: - apk add git - sh ./tools/check-changelog @@ -71,6 +78,7 @@ check-changelog: .using-ci-base: tags: - amd64 + artifacts: *default_artifacts build-1.15.8-otp-26: extends: @@ -101,8 +109,12 @@ spec-build: artifacts: paths: - spec.json + reports: + dotenv: build.env + expire_in: 42 years script: - mix pleroma.openapi_spec spec.json + - echo "SPEC_BUILD_JOB_ID=$CI_JOB_ID" >> build.env benchmark: extends: @@ -153,6 +165,7 @@ unit-testing-1.15.8-otp-26: - su testuser -c "HOME=/home/testuser mix pleroma.test_runner --cover --preload-modules" coverage: '/^Line total: ([^ ]*%)$/' artifacts: + expire_in: 30 days reports: coverage_report: coverage_format: cobertura @@ -171,6 +184,7 @@ unit-testing-1.18.3-otp-27: formatting-1.15: extends: .build_changes_policy + artifacts: *default_artifacts image: &formatting_elixir elixir:1.15-alpine stage: lint cache: *testing_cache_policy @@ -185,6 +199,7 @@ formatting-1.15: cycles-1.15: extends: .build_changes_policy + artifacts: *default_artifacts image: *formatting_elixir stage: lint cache: {} @@ -208,7 +223,7 @@ dialyzer: - .using-ci-base stage: lint allow_failure: true - when: manual + when: manual cache: *testing_cache_policy tags: - feld @@ -217,15 +232,13 @@ dialyzer: docs-deploy: stage: deploy - cache: *testing_cache_policy - image: alpine:latest + trigger: + project: pleroma/docs + branch: master + strategy: depend only: - stable@pleroma/pleroma - develop@pleroma/pleroma - before_script: - - apk add curl - script: - - curl --fail-with-body -X POST -F"token=$DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline review_app: image: alpine:3.9 stage: deploy @@ -241,6 +254,7 @@ review_app: except: - master - develop + artifacts: *default_artifacts script: - echo "$CI_ENVIRONMENT_SLUG" - mkdir -p ~/.ssh @@ -257,21 +271,19 @@ review_app: spec-deploy: stage: deploy - artifacts: - paths: - - spec.json + trigger: + project: pleroma/api-docs + branch: master + strategy: depend only: - develop@pleroma/pleroma - image: alpine:latest - before_script: - - apk add curl - script: - - curl --fail-with-body -X POST -F"token=$API_DOCS_PIPELINE_TRIGGER" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" -F"variables[JOB_REF]=$CI_JOB_ID" https://git.pleroma.social/api/v4/projects/1130/trigger/pipeline - + variables: + SPEC_BUILD_JOB_ID: $SPEC_BUILD_JOB_ID stop_review_app: image: alpine:3.9 stage: deploy + artifacts: *default_artifacts before_script: - apk update && apk add openssh-client git when: manual diff --git a/changelog.d/authorized_fetch.fix b/changelog.d/authorized_fetch.fix @@ -0,0 +1 @@ +Fix fetching public keys with authorized fetch enabled +\ No newline at end of file diff --git a/changelog.d/ci-artifacts.skip b/changelog.d/ci-artifacts.skip diff --git a/changelog.d/docs.skip b/changelog.d/docs.skip @@ -0,0 +1 @@ +Update *Differences in Mastodon API responses from vanilla Mastodon* +\ No newline at end of file diff --git a/changelog.d/endorsements-api.change b/changelog.d/endorsements-api.change @@ -0,0 +1 @@ +Support new Mastodon API for endorsed accounts diff --git a/changelog.d/fix-lists-bcc.fix b/changelog.d/fix-lists-bcc.fix @@ -0,0 +1 @@ +Fix publisher when publishing to a list of users diff --git a/changelog.d/instance-view-timeline-access.add b/changelog.d/instance-view-timeline-access.add @@ -0,0 +1 @@ +Add `timelines_access` to InstanceView diff --git a/changelog.d/local-nickname-regex.fix b/changelog.d/local-nickname-regex.fix @@ -0,0 +1 @@ +Use end-of-string in regex for local `get_by_nickname` diff --git a/changelog.d/notification-cleanup.skip b/changelog.d/notification-cleanup.skip diff --git a/changelog.d/plaroma.skip b/changelog.d/plaroma.skip @@ -0,0 +1 @@ +i don't think it's called plaroma +\ No newline at end of file diff --git a/changelog.d/remote-url.fix b/changelog.d/remote-url.fix @@ -0,0 +1 @@ +`remote_url` links to unproxied URL diff --git a/changelog.d/rss-redirect.change b/changelog.d/rss-redirect.change @@ -0,0 +1 @@ +Redirect /users/:nickname.rss to /users/:nickname/feed.rss instead of .atom +\ No newline at end of file diff --git a/changelog.d/status-push-notification.fix b/changelog.d/status-push-notification.fix @@ -0,0 +1 @@ +Send push notifications for statuses from subscribed accounts diff --git a/changelog.d/translation-provider-mozhi.add b/changelog.d/translation-provider-mozhi.add @@ -0,0 +1 @@ +Support Mozhi translation provider diff --git a/config/description.exs b/config/description.exs @@ -3559,6 +3559,7 @@ config :pleroma, :config_description, [ suggestions: [ Pleroma.Language.Translation.Deepl, Pleroma.Language.Translation.Libretranslate, + Pleroma.Language.Translation.Mozhi, Pleroma.Language.Translation.TranslateLocally ] }, @@ -3592,11 +3593,24 @@ config :pleroma, :config_description, [ }, %{ group: {:subgroup, Pleroma.Language.Translation.TranslateLocally}, - key: :intermediate_language, + key: :intermediary_language, label: - "translateLocally intermediate language (used when direct source->target model is not available)", + "translateLocally intermediary language (used when direct source->target model is not available)", type: :string, suggestions: ["en"] + }, + %{ + group: {:subgroup, Pleroma.Language.Translation.Mozhi}, + key: :base_url, + label: "Mozhi instance URL", + type: :string + }, + %{ + group: {:subgroup, Pleroma.Language.Translation.Mozhi}, + key: :engine, + label: "Engine used for Mozhi", + type: :string, + suggestions: ["libretranslate"] } ] } diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md @@ -40,10 +40,13 @@ Has these additional fields under the `pleroma` object: - `parent_visible`: If the parent of this post is visible to the user or not. - `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise. - `quotes_count`: the count of status quotes. -- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen. - `bookmark_folder`: the ID of the folder bookmark is stored within (if any). - `list_id`: the ID of the list the post is addressed to (if any, only returned to author). +Has these additional fields under the `poll.pleroma` object: + +- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen. + The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes: - `content_type`: The content type of the status source. @@ -98,6 +101,9 @@ Endpoints which accept `with_relationships` parameter: - `/api/v1/accounts/:id/followers` - `/api/v1/accounts/:id/following` - `/api/v1/mutes` +- `/api/v1/blocks` +- `/api/v1/search` +- `/api/v2/search` Has these additional fields under the `pleroma` object: diff --git a/docs/installation/optional/media_graphics_packages.md b/docs/installation/optional/media_graphics_packages.md @@ -16,7 +16,7 @@ Note: the packages are not required with the current default settings of Pleroma It is required for the following Pleroma features: -* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Plaroma.Upload/filters` in `config/config.exs`) +* `Pleroma.Upload.Filters.Mogrify`, `Pleroma.Upload.Filters.Mogrifun` upload filters (related config: `Pleroma.Upload/filters` in `config/config.exs`) * Media preview proxy for still images (related config: `media_preview_proxy/enabled` in `config/config.exs`) ## `ffmpeg` @@ -33,5 +33,5 @@ It is required for the following Pleroma features: It is required for the following Pleroma features: -* `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`) -* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Plaroma.Upload/filters` in `config/config.exs`) +* `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`) +* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`) diff --git a/lib/pleroma/language/translation/mozhi.ex b/lib/pleroma/language/translation/mozhi.ex @@ -0,0 +1,109 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Language.Translation.Mozhi do + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + + alias Pleroma.Language.Translation.Provider + + use Provider + + @behaviour Provider + + @name "Mozhi" + + @impl Provider + def configured?, do: not_empty_string(base_url()) and not_empty_string(engine()) + + @impl Provider + def translate(content, source_language, target_language) do + endpoint = + base_url() + |> URI.merge("/api/translate") + |> URI.to_string() + + case Pleroma.HTTP.get( + endpoint <> + "?" <> + URI.encode_query(%{ + engine: engine(), + text: content, + from: source_language, + to: target_language + }), + [{"Accept", "application/json"}] + ) do + {:ok, %{status: 200} = res} -> + %{ + "translated-text" => content, + "source_language" => source_language + } = Jason.decode!(res.body) + + {:ok, + %{ + content: content, + detected_source_language: source_language, + provider: @name + }} + + _ -> + {:error, :internal_server_error} + end + end + + @impl Provider + def supported_languages(type) when type in [:source, :target] do + path = + case type do + :source -> "/api/source_languages" + :target -> "/api/target_languages" + end + + endpoint = + base_url() + |> URI.merge(path) + |> URI.to_string() + + case Pleroma.HTTP.get( + endpoint <> + "?" <> + URI.encode_query(%{ + engine: engine() + }), + [{"Accept", "application/json"}] + ) do + {:ok, %{status: 200} = res} -> + languages = + Jason.decode!(res.body) + |> Enum.map(fn %{"Id" => language} -> language end) + + {:ok, languages} + + _ -> + {:error, :internal_server_error} + end + end + + @impl Provider + def languages_matrix do + with {:ok, source_languages} <- supported_languages(:source), + {:ok, target_languages} <- supported_languages(:target) do + {:ok, + Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)} + else + {:error, error} -> {:error, error} + end + end + + @impl Provider + def name, do: @name + + defp base_url do + Pleroma.Config.get([__MODULE__, :base_url]) + end + + defp engine do + Pleroma.Config.get([__MODULE__, :engine]) + end +end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex @@ -526,9 +526,7 @@ defmodule Pleroma.Notification do %Activity{data: %{"type" => "Create"}} = activity, local_only ) do - notification_enabled_ap_ids = - [] - |> Utils.maybe_notify_subscribers(activity) + notification_enabled_ap_ids = Utils.get_notified_subscribers(activity) potential_receivers = User.get_users_from_set(notification_enabled_ap_ids, local_only: local_only) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex @@ -54,7 +54,7 @@ defmodule Pleroma.Signature do def fetch_public_key(conn) do with {:ok, actor_id} <- get_actor_id(conn), - {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do + {:ok, public_key} <- User.get_or_fetch_public_key_for_ap_id(actor_id) do {:ok, public_key} else e -> diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex @@ -233,8 +233,8 @@ defmodule Pleroma.User do for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <- @user_relationships_config do # `def blocked_users_relation/2`, `def muted_users_relation/2`, - # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`, - # `def subscriber_users/2`, `def endorsed_users_relation/2` + # `def reblog_muted_users_relation/2`, `def notification_muted_users_relation/2`, + # `def subscriber_users_relation/2`, `def endorsed_users_relation/2` def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do target_users_query = assoc(user, unquote(outgoing_relation_target)) @@ -1357,7 +1357,7 @@ defmodule Pleroma.User do @spec get_by_nickname(String.t()) :: User.t() | nil def get_by_nickname(nickname) do Repo.get_by(User, nickname: nickname) || - if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do + if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()}$)i, nickname) do Repo.get_by(User, nickname: local_nickname(nickname)) end end @@ -2307,6 +2307,15 @@ defmodule Pleroma.User do def public_key(_), do: {:error, "key not found"} + def get_or_fetch_public_key_for_ap_id(ap_id) do + with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id), + {:ok, public_key} <- public_key(user) do + {:ok, public_key} + else + _ -> :error + end + end + def get_public_key_for_ap_id(ap_id) do with %User{} = user <- get_cached_by_ap_id(ap_id), {:ok, public_key} <- public_key(user) do diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex @@ -331,17 +331,21 @@ defmodule Pleroma.Web.ActivityPub.Publisher do Repo.checkout(fn -> Enum.each([priority_inboxes, other_inboxes], fn inboxes -> Enum.each(inboxes, fn inbox -> - %User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end) + {%User{ap_id: ap_id}, priority} = + get_user_with_priority(inbox, priority_recipients, recipients) # Get all the recipients on the same host and add them to cc. Otherwise, a remote # instance would only accept a first message for the first recipient and ignore the rest. cc = get_cc_ap_ids(ap_id, recipients) - __MODULE__.enqueue_one(%{ - inbox: inbox, - cc: cc, - activity_id: activity.id - }) + __MODULE__.enqueue_one( + %{ + inbox: inbox, + cc: cc, + activity_id: activity.id + }, + priority: priority + ) end) end) end) @@ -403,4 +407,15 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end def gather_nodeinfo_protocol_names, do: ["activitypub"] + + defp get_user_with_priority(inbox, priority_recipients, recipients) do + [{priority_recipients, 0}, {recipients, 1}] + |> Enum.find_value(fn {recipients, priority} -> + with %User{} = user <- Enum.find(recipients, fn actor -> actor.inbox == inbox end) do + {user, priority} + else + _ -> nil + end + end) + end end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -398,6 +398,28 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end + def endorsements_operation do + %Operation{ + tags: ["Retrieve account information"], + summary: "Endorsements", + description: "Returns endorsed accounts", + operationId: "AccountController.endorsements", + parameters: [ + with_relationships_param(), + %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} + ], + responses: %{ + 200 => + Operation.response( + "Array of Accounts", + "application/json", + array_of_accounts() + ), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + def remove_from_followers_operation do %Operation{ tags: ["Account actions"], @@ -500,11 +522,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def endorsements_operation do + def own_endorsements_operation do %Operation{ tags: ["Retrieve account information"], summary: "Endorsements", - operationId: "AccountController.endorsements", + operationId: "AccountController.own_endorsements", description: "Returns endorsed accounts", security: [%{"oAuth" => ["read:accounts"]}], responses: %{ diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex @@ -64,25 +64,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do } end - def endorsements_operation do - %Operation{ - tags: ["Retrieve account information"], - summary: "Endorsements", - description: "Returns endorsed accounts", - operationId: "PleromaAPI.AccountController.endorsements", - parameters: [with_relationships_param(), id_param()], - responses: %{ - 200 => - Operation.response( - "Array of Accounts", - "application/json", - AccountOperation.array_of_accounts() - ), - 404 => Operation.response("Not Found", "application/json", ApiError) - } - } - end - def subscribe_operation do %Operation{ deprecated: true, diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex @@ -402,8 +402,20 @@ defmodule Pleroma.Web.CommonAPI.Utils do def maybe_notify_mentioned_recipients(recipients, _), do: recipients - def maybe_notify_subscribers( - recipients, + def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do + with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do + user + |> User.get_followers() + |> Enum.map(& &1.ap_id) + |> Enum.concat(recipients) + else + _e -> recipients + end + end + + def maybe_notify_followers(recipients, _), do: recipients + + def get_notified_subscribers( %Activity{data: %{"actor" => actor, "type" => "Create"}} = activity ) do # Do not notify subscribers if author is making a reply @@ -416,26 +428,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Enum.filter(&Visibility.visible_for_user?(activity, &1)) |> Enum.map(& &1.ap_id) - recipients ++ subscriber_ids + subscriber_ids else - _e -> recipients - end - end - - def maybe_notify_subscribers(recipients, _), do: recipients - - def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do - with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do - user - |> User.get_followers() - |> Enum.map(& &1.ap_id) - |> Enum.concat(recipients) - else - _e -> recipients + _e -> [] end end - def maybe_notify_followers(recipients, _), do: recipients + def get_notified_subscribers(_), do: [] def maybe_extract_mentions(%{"tag" => tag}) do tag diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex @@ -28,9 +28,12 @@ defmodule Pleroma.Web.Feed.UserController do ActivityPubController.call(conn, :user) end - def feed_redirect(conn, %{"nickname" => nickname}) do + def feed_redirect(%{assigns: assigns} = conn, %{"nickname" => nickname}) do + format = Map.get(assigns, :format, "atom") + format = if format in ["atom", "rss"], do: format, else: "atom" + with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do - redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom") + redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.#{format}") end end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -38,7 +38,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} - when action in [:show, :followers, :following] + when action in [:show, :followers, :following, :endorsements] ) plug( @@ -50,7 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, %{scopes: ["read:accounts"]} - when action in [:verify_credentials, :endorsements] + when action in [:verify_credentials, :endorsements, :own_endorsements] ) plug( @@ -89,7 +89,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @relationship_actions [:follow, :unfollow, :remove_from_followers] @needs_account ~W( followers following lists follow unfollow mute unmute block unblock - note endorse unendorse remove_from_followers + note endorse unendorse endorsements remove_from_followers )a plug( @@ -555,6 +555,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end end + @doc "GET /api/v1/accounts/:id/endorsements" + def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do + users = + user + |> User.endorsed_users_relation(_restrict_deactivated = true) + |> Pleroma.Repo.all() + + conn + |> render("index.json", + for: for_user, + users: users, + as: :user, + embed_relationships: embed_relationships?(params) + ) + end + @doc "POST /api/v1/accounts/:id/remove_from_followers" def remove_from_followers(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do {:error, "Can not unfollow yourself"} @@ -631,7 +647,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do end @doc "GET /api/v1/endorsements" - def endorsements(%{assigns: %{user: user}} = conn, params) do + def own_endorsements(%{assigns: %{user: user}} = conn, params) do users = user |> User.endorsed_users_relation(_restrict_deactivated = true) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -257,10 +257,34 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do vapid: %{ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) }, - translation: %{enabled: Pleroma.Language.Translation.configured?()} + translation: %{enabled: Pleroma.Language.Translation.configured?()}, + timelines_access: %{ + live_feeds: timelines_access(), + hashtag_feeds: timelines_access(), + # not implemented in Pleroma + trending_link_feeds: %{ + local: "disabled", + remote: "disabled" + } + } }) end + defp timelines_access do + %{ + local: timeline_access(:local), + remote: timeline_access(:federated) + } + end + + defp timeline_access(kind) do + if Config.restrict_unauthenticated_access?(:timelines, kind) do + "authenticated" + else + "public" + end + end + defp pleroma_configuration(instance) do base_urls = %{} diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -602,7 +602,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def render("attachment.json", %{attachment: attachment}) do [attachment_url | _] = attachment["url"] media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image" - href = attachment_url["href"] |> MediaProxy.url() + href_remote = attachment_url["href"] + href = href_remote |> MediaProxy.url() href_preview = attachment_url["href"] |> MediaProxy.preview_url() meta = render("attachment_meta.json", %{attachment: attachment}) @@ -641,7 +642,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do %{ id: attachment_id, url: href, - remote_url: href, + remote_url: href_remote, preview_url: href_preview, text_url: href, type: type, diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do only: [ json_response: 3, add_link_headers: 2, - embed_relationships?: 1, assign_account_by_id: 2 ] @@ -47,12 +46,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do plug( OAuthScopesPlug, - %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} - when action == :endorsements - ) - - plug( - OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :birthdays ) @@ -60,7 +53,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do plug( :assign_account_by_id - when action in [:favourites, :endorsements, :subscribe, :unsubscribe] + when action in [:favourites, :subscribe, :unsubscribe] ) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation @@ -109,22 +102,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do ) end - @doc "GET /api/v1/pleroma/accounts/:id/endorsements" - def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do - users = - user - |> User.endorsed_users_relation(_restrict_deactivated = true) - |> Pleroma.Repo.all() - - conn - |> render("index.json", - for: for_user, - users: users, - as: :user, - embed_relationships: embed_relationships?(params) - ) - end - @doc "POST /api/v1/pleroma/accounts/:id/subscribe" def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do with {:ok, _subscription} <- User.subscribe(user, subscription_target) do diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do end # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength - @supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a + @supported_alert_types ~w[follow favourite mention status reblog poll pleroma:chat_mention pleroma:emoji_reaction]a defp alerts(%{data: %{alerts: alerts}}) do alerts = Map.take(alerts, @supported_alert_types) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex @@ -608,7 +608,6 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:api) get("/accounts/:id/favourites", AccountController, :favourites) - get("/accounts/:id/endorsements", AccountController, :endorsements) get("/statuses/:id/quotes", StatusController, :quotes) end @@ -637,6 +636,11 @@ defmodule Pleroma.Web.Router do get("/accounts/:id/scrobbles", ScrobbleController, :index) end + scope "/api/v1/pleroma", Pleroma.Web.MastodonAPI do + pipe_through(:api) + get("/accounts/:id/endorsements", AccountController, :endorsements) + end + scope "/api/v2/pleroma", Pleroma.Web.PleromaAPI do scope [] do pipe_through(:authenticated_api) @@ -653,7 +657,7 @@ defmodule Pleroma.Web.Router do get("/accounts/relationships", AccountController, :relationships) get("/accounts/familiar_followers", AccountController, :familiar_followers) get("/accounts/:id/lists", AccountController, :lists) - get("/endorsements", AccountController, :endorsements) + get("/endorsements", AccountController, :own_endorsements) get("/blocks", AccountController, :blocks) get("/mutes", AccountController, :mutes) @@ -667,6 +671,8 @@ defmodule Pleroma.Web.Router do post("/accounts/:id/note", AccountController, :note) post("/accounts/:id/pin", AccountController, :endorse) post("/accounts/:id/unpin", AccountController, :unendorse) + post("/accounts/:id/endorse", AccountController, :endorse) + post("/accounts/:id/unendorse", AccountController, :unendorse) post("/accounts/:id/remove_from_followers", AccountController, :remove_from_followers) get("/conversations", ConversationController, :index) @@ -782,6 +788,7 @@ defmodule Pleroma.Web.Router do get("/accounts/:id/statuses", AccountController, :statuses) get("/accounts/:id/followers", AccountController, :followers) get("/accounts/:id/following", AccountController, :following) + get("/accounts/:id/endorsements", AccountController, :endorsements) get("/accounts/:id", AccountController, :show) post("/accounts", AccountController, :create) diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs @@ -1881,6 +1881,11 @@ defmodule Pleroma.UserTest do end end + test "get_or_fetch_public_key_for_ap_id fetches a user that's not in the db" do + assert {:ok, _key} = + User.get_or_fetch_public_key_for_ap_id("http://mastodon.example.org/users/admin") + end + test "get_public_key_for_ap_id returns correctly for user that's not in the db" do assert :error = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") end diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs @@ -332,6 +332,33 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do ) end + test "activity with BCC is published to a list member." do + actor = insert(:user) + {:ok, list} = Pleroma.List.create("list", actor) + list_member = insert(:user, %{local: false}) + + Pleroma.List.follow(list, list_member) + + note_activity = + insert(:note_activity, + # recipients: [follower.ap_id], + data_attrs: %{"bcc" => [list.ap_id]} + ) + + res = Publisher.publish(actor, note_activity) + assert res == :ok + + assert_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "params" => %{ + inbox: list_member.inbox, + activity_id: note_activity.id + } + } + ) + end + test "publishes a delete activity to peers who signed fetch requests to the create acitvity/object." do fetcher = insert(:user, diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs @@ -282,6 +282,21 @@ defmodule Pleroma.Web.Feed.UserControllerTest do "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.atom" end + test "redirects to rss feed when explicitly requested", %{conn: conn} do + note_activity = insert(:note_activity) + user = User.get_cached_by_ap_id(note_activity.data["actor"]) + + conn = + conn + |> put_req_header("accept", "application/xml") + |> get("/users/#{user.nickname}.rss") + + assert conn.status == 302 + + assert redirected_to(conn) == + "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.rss" + end + test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do response = conn diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -2134,7 +2134,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert %{"id" => ^id1, "endorsed" => true} = conn |> put_req_header("content-type", "application/json") - |> post("/api/v1/accounts/#{id1}/pin") + |> post("/api/v1/accounts/#{id1}/endorse") |> json_response_and_validate_schema(200) assert [%{"id" => ^id1}] = @@ -2153,7 +2153,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert %{"id" => ^id1, "endorsed" => false} = conn |> put_req_header("content-type", "application/json") - |> post("/api/v1/accounts/#{id1}/unpin") + |> post("/api/v1/accounts/#{id1}/unendorse") |> json_response_and_validate_schema(200) assert [] = @@ -2172,15 +2172,40 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do conn |> put_req_header("content-type", "application/json") - |> post("/api/v1/accounts/#{id1}/pin") + |> post("/api/v1/accounts/#{id1}/endorse") |> json_response_and_validate_schema(200) assert %{"error" => "You have already pinned the maximum number of users"} = conn |> assign(:user, user) - |> post("/api/v1/accounts/#{id2}/pin") + |> post("/api/v1/accounts/#{id2}/endorse") |> json_response_and_validate_schema(400) end + + test "returns a list of pinned accounts", %{conn: conn} do + clear_config([:instance, :max_endorsed_users], 3) + + %{id: id1} = user1 = insert(:user) + %{id: id2} = user2 = insert(:user) + %{id: id3} = user3 = insert(:user) + + CommonAPI.follow(user2, user1) + CommonAPI.follow(user3, user1) + + User.endorse(user1, user2) + User.endorse(user1, user3) + + [%{"id" => ^id2}, %{"id" => ^id3}] = + conn + |> get("/api/v1/accounts/#{id1}/endorsements") + |> json_response_and_validate_schema(200) + end + + test "returns 404 error when specified user is not exist", %{conn: conn} do + conn = get(conn, "/api/v1/accounts/test/endorsements") + + assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"} + end end describe "familiar followers" do diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -194,4 +194,28 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do refute Map.has_key?(result["pleroma"]["metadata"]["base_urls"], "media_proxy") refute Map.has_key?(result["pleroma"]["metadata"]["base_urls"], "upload") end + + test "display timeline access restrictions", %{conn: conn} do + clear_config([:restrict_unauthenticated, :timelines, :local], true) + clear_config([:restrict_unauthenticated, :timelines, :federated], false) + + conn = get(conn, "/api/v2/instance") + + assert result = json_response_and_validate_schema(conn, 200) + + assert result["configuration"]["timelines_access"] == %{ + "live_feeds" => %{ + "local" => "authenticated", + "remote" => "public" + }, + "hashtag_feeds" => %{ + "local" => "authenticated", + "remote" => "public" + }, + "trending_link_feeds" => %{ + "local" => "disabled", + "remote" => "disabled" + } + } + end end diff --git a/test/pleroma/web/pleroma_api/controllers/account_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/account_controller_test.exs @@ -280,35 +280,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do end end - describe "account endorsements" do - test "returns a list of pinned accounts", %{conn: conn} do - %{id: id1} = user1 = insert(:user) - %{id: id2} = user2 = insert(:user) - %{id: id3} = user3 = insert(:user) - - CommonAPI.follow(user2, user1) - CommonAPI.follow(user3, user1) - - User.endorse(user1, user2) - User.endorse(user1, user3) - - response = - conn - |> get("/api/v1/pleroma/accounts/#{id1}/endorsements") - |> json_response_and_validate_schema(200) - - assert length(response) == 2 - assert Enum.any?(response, fn user -> user["id"] == id2 end) - assert Enum.any?(response, fn user -> user["id"] == id3 end) - end - - test "returns 404 error when specified user is not exist", %{conn: conn} do - conn = get(conn, "/api/v1/pleroma/accounts/test/endorsements") - - assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"} - end - end - describe "birthday reminders" do test "returns a list of friends having birthday on specified day" do %{user: user, conn: conn} = oauth_access(["read:accounts"])