diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 29ee24a054..bfd9bf4147 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -208,7 +208,7 @@ docs-deploy: before_script: - apk add curl script: - - curl --fail-with-body -X POST -F"token=$CI_JOB_TOKEN" -F'ref=master' -F"variables[BRANCH]=$CI_COMMIT_REF_NAME" https://git.pleroma.social/api/v4/projects/673/trigger/pipeline + - 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 @@ -249,7 +249,7 @@ spec-deploy: before_script: - apk add curl script: - - curl --fail-with-body -X POST -F"token=$CI_JOB_TOKEN" -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 + - 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 stop_review_app: diff --git a/changelog.d/assign-app-user-oom.fix b/changelog.d/assign-app-user-oom.fix new file mode 100644 index 0000000000..ac1de71597 --- /dev/null +++ b/changelog.d/assign-app-user-oom.fix @@ -0,0 +1 @@ +Fix AssignAppUser migration OOM diff --git a/changelog.d/deepl-json.fix b/changelog.d/deepl-json.fix new file mode 100644 index 0000000000..ee6f8664ed --- /dev/null +++ b/changelog.d/deepl-json.fix @@ -0,0 +1 @@ +Use JSON for DeepL API requests diff --git a/changelog.d/delete-instance.change b/changelog.d/delete-instance.change new file mode 100644 index 0000000000..9d84dac54f --- /dev/null +++ b/changelog.d/delete-instance.change @@ -0,0 +1 @@ +Deleting an instance queues individual jobs for each user that needs to be deleted from the server. diff --git a/changelog.d/dislike-activity.add b/changelog.d/dislike-activity.add new file mode 100644 index 0000000000..1fcbda78b7 --- /dev/null +++ b/changelog.d/dislike-activity.add @@ -0,0 +1 @@ +Support Dislike activity, as sent by Mitra and Friendica, by changing it into a thumbs-down EmojiReact \ No newline at end of file diff --git a/changelog.d/expose-markup-configuration.add b/changelog.d/expose-markup-configuration.add new file mode 100644 index 0000000000..8c7f356976 --- /dev/null +++ b/changelog.d/expose-markup-configuration.add @@ -0,0 +1 @@ +Expose markup configuration in InstanceView diff --git a/changelog.d/mrf-quietreply.add b/changelog.d/mrf-quietreply.add new file mode 100644 index 0000000000..4ed20bce60 --- /dev/null +++ b/changelog.d/mrf-quietreply.add @@ -0,0 +1 @@ +Added MRF.QuietReply which prevents replies to public posts from being published to the timelines diff --git a/changelog.d/preserve-public-cc.fix b/changelog.d/preserve-public-cc.fix new file mode 100644 index 0000000000..1b20ce9adf --- /dev/null +++ b/changelog.d/preserve-public-cc.fix @@ -0,0 +1 @@ +Fix federation issue where Public visibility information in cc field was lost when sent to remote servers, causing posts to appear with inconsistent visibility across instances diff --git a/changelog.d/relax-also-known-as.change b/changelog.d/relax-also-known-as.change new file mode 100644 index 0000000000..800c3e72a3 --- /dev/null +++ b/changelog.d/relax-also-known-as.change @@ -0,0 +1 @@ +Relax alsoKnownAs requirements to just URI, not necessarily HTTP(S) \ No newline at end of file diff --git a/changelog.d/remove-forgotten-OTPVersion-usage.skip b/changelog.d/remove-forgotten-OTPVersion-usage.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/scrobbles.change b/changelog.d/scrobbles.change new file mode 100644 index 0000000000..ed1777b2d4 --- /dev/null +++ b/changelog.d/scrobbles.change @@ -0,0 +1 @@ +Change scrobble external link param name to use snake case \ No newline at end of file diff --git a/changelog.d/toctou-mkdir.fix b/changelog.d/toctou-mkdir.fix new file mode 100644 index 0000000000..b070db1a05 --- /dev/null +++ b/changelog.d/toctou-mkdir.fix @@ -0,0 +1 @@ +Backport [Elixir PR 14242](https://github.com/elixir-lang/elixir/pull/14242) fixing racy mkdir and lack of error handling of parent directory creation \ No newline at end of file diff --git a/changelog.d/tos-setting.add b/changelog.d/tos-setting.add new file mode 100644 index 0000000000..db9b0d5f28 --- /dev/null +++ b/changelog.d/tos-setting.add @@ -0,0 +1 @@ +Allow Terms of Service panel behaviour to be configurable diff --git a/config/config.exs b/config/config.exs index d164f8389d..805cd0d629 100644 --- a/config/config.exs +++ b/config/config.exs @@ -306,6 +306,7 @@ config :pleroma, :frontend_configurations, collapseMessageWithSubject: false, disableChat: false, greentext: false, + embeddedToS: true, hideFilteredStatuses: false, hideMutedPosts: false, hidePostStats: false, diff --git a/config/description.exs b/config/description.exs index 2f7dc30a09..e20fa4b28a 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1261,6 +1261,7 @@ config :pleroma, :config_description, [ background: "/static/aurora_borealis.jpg", collapseMessageWithSubject: false, greentext: false, + embeddedToS: true, hideFilteredStatuses: false, hideMutedPosts: false, hidePostStats: false, @@ -1312,6 +1313,12 @@ config :pleroma, :config_description, [ type: :boolean, description: "Enables green text on lines prefixed with the > character" }, + %{ + key: :embeddedToS, + label: "Embedded ToS panel", + type: :boolean, + description: "Hide Terms of Service panel decorations on About and Registration pages" + }, %{ key: :hideFilteredStatuses, label: "Hide Filtered Statuses", diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md index 000d7d27d9..b17f61cbbb 100644 --- a/docs/development/API/pleroma_api.md +++ b/docs/development/API/pleroma_api.md @@ -671,6 +671,7 @@ Audio scrobbling in Pleroma is **deprecated**. "artist": "Some Artist", "album": "Some Album", "length": 180000, + "external_link": "https://www.last.fm/music/Some+Artist/_/Some+Title", "created_at": "2019-09-28T12:40:45.000Z" } ] diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 0dc30549c4..143af5cdd3 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -271,7 +271,7 @@ defmodule Mix.Tasks.Pleroma.Instance do [config_dir, psql_dir, static_dir, uploads_dir] |> Enum.reject(&File.exists?/1) |> Enum.each(fn dir -> - File.mkdir_p!(dir) + Pleroma.Backports.mkdir_p!(dir) File.chmod!(dir, 0o700) end) diff --git a/lib/mix/tasks/pleroma/robots_txt.ex b/lib/mix/tasks/pleroma/robots_txt.ex index 5124c7c40a..e741f3cf07 100644 --- a/lib/mix/tasks/pleroma/robots_txt.ex +++ b/lib/mix/tasks/pleroma/robots_txt.ex @@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxt do static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") if !File.exists?(static_dir) do - File.mkdir_p!(static_dir) + Pleroma.Backports.mkdir_p!(static_dir) end robots_txt_path = Path.join(static_dir, "robots.txt") diff --git a/lib/pleroma/backports.ex b/lib/pleroma/backports.ex new file mode 100644 index 0000000000..68cb7b990e --- /dev/null +++ b/lib/pleroma/backports.ex @@ -0,0 +1,72 @@ +# Copyright 2012 Plataformatec +# Copyright 2021 The Elixir Team +# SPDX-License-Identifier: Apache-2.0 + +defmodule Pleroma.Backports do + import File, only: [dir?: 1] + + # + # To be removed when we require Elixir 1.19 + @doc """ + Tries to create the directory `path`. + + Missing parent directories are created. Returns `:ok` if successful, or + `{:error, reason}` if an error occurs. + + Typical error reasons are: + + * `:eacces` - missing search or write permissions for the parent + directories of `path` + * `:enospc` - there is no space left on the device + * `:enotdir` - a component of `path` is not a directory + + """ + @spec mkdir_p(Path.t()) :: :ok | {:error, File.posix() | :badarg} + def mkdir_p(path) do + do_mkdir_p(IO.chardata_to_string(path)) + end + + defp do_mkdir_p("/") do + :ok + end + + defp do_mkdir_p(path) do + parent = Path.dirname(path) + + if parent == path do + :ok + else + case do_mkdir_p(parent) do + :ok -> + case :file.make_dir(path) do + {:error, :eexist} -> + if dir?(path), do: :ok, else: {:error, :enotdir} + + other -> + other + end + + e -> + e + end + end + end + + @doc """ + Same as `mkdir_p/1`, but raises a `File.Error` exception in case of failure. + Otherwise `:ok`. + """ + @spec mkdir_p!(Path.t()) :: :ok + def mkdir_p!(path) do + case mkdir_p(path) do + :ok -> + :ok + + {:error, reason} -> + raise File.Error, + reason: reason, + action: "make directory (with -p)", + path: IO.chardata_to_string(path) + end + end +end diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 3762c00350..92ca11494a 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -100,6 +100,7 @@ defmodule Pleroma.Constants do "Add", "Remove", "Like", + "Dislike", "Announce", "Undo", "Flag", @@ -115,6 +116,7 @@ defmodule Pleroma.Constants do "Flag", "Follow", "Like", + "Dislike", "EmojiReact", "Announce" ] diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index c58748d3ce..99fa1994f8 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -488,7 +488,7 @@ defmodule Pleroma.Emoji.Pack do with true <- String.contains?(file_path, "/"), path <- Path.dirname(file_path), false <- File.exists?(path) do - File.mkdir_p!(path) + Pleroma.Backports.mkdir_p!(path) end end @@ -536,7 +536,7 @@ defmodule Pleroma.Emoji.Pack do emoji_path = emoji_path() # Create the directory first if it does not exist. This is probably the first request made # with the API so it should be sufficient - with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)}, + with {:create_dir, :ok} <- {:create_dir, Pleroma.Backports.mkdir_p(emoji_path)}, {:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do {:ok, Enum.sort(results)} else @@ -561,7 +561,7 @@ defmodule Pleroma.Emoji.Pack do end defp unzip(archive, pack_info, remote_pack, local_pack) do - with :ok <- File.mkdir_p!(local_pack.path) do + with :ok <- Pleroma.Backports.mkdir_p!(local_pack.path) do files = Enum.map(remote_pack["files"], fn {_, path} -> path end) # Fallback cannot contain a pack.json file files = if pack_info[:fallback], do: files, else: ["pack.json" | files] diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex index fe7f525eab..e651d7d9db 100644 --- a/lib/pleroma/frontend.ex +++ b/lib/pleroma/frontend.ex @@ -66,7 +66,7 @@ defmodule Pleroma.Frontend do def unzip(zip, dest) do File.rm_rf!(dest) - File.mkdir_p!(dest) + Pleroma.Backports.mkdir_p!(dest) case Pleroma.SafeZip.unzip_data(zip, dest) do {:ok, _} -> :ok @@ -90,7 +90,7 @@ defmodule Pleroma.Frontend do defp install_frontend(frontend_info, source, dest) do from = frontend_info["build_dir"] || "dist" File.rm_rf!(dest) - File.mkdir_p!(dest) + Pleroma.Backports.mkdir_p!(dest) File.cp_r!(Path.join([source, from]), dest) :ok end diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 7b71279732..fb0b9d7f0d 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Instances.Instance do alias Pleroma.Instances.Instance alias Pleroma.Maps alias Pleroma.Repo - alias Pleroma.User alias Pleroma.Workers.DeleteWorker use Ecto.Schema @@ -294,16 +293,4 @@ defmodule Pleroma.Instances.Instance do DeleteWorker.new(%{"op" => "delete_instance", "host" => host}) |> Oban.insert() end - - def perform(:delete_instance, host) when is_binary(host) do - User.Query.build(%{nickname: "@#{host}"}) - |> Repo.chunk_stream(100, :batches) - |> Stream.each(fn users -> - users - |> Enum.each(fn user -> - User.perform(:delete, user) - end) - end) - |> Stream.run() - end end diff --git a/lib/pleroma/language/translation/deepl.ex b/lib/pleroma/language/translation/deepl.ex index e027035b4d..aaaac9b0fb 100644 --- a/lib/pleroma/language/translation/deepl.ex +++ b/lib/pleroma/language/translation/deepl.ex @@ -24,17 +24,15 @@ defmodule Pleroma.Language.Translation.Deepl do |> URI.to_string() case Pleroma.HTTP.post( - endpoint <> - "?" <> - URI.encode_query(%{ - text: content, - source_lang: source_language |> String.upcase(), - target_lang: target_language, - tag_handling: "html" - }), - "", + endpoint, + Jason.encode!(%{ + text: [content], + source_lang: source_language |> String.upcase(), + target_lang: target_language, + tag_handling: "html" + }), [ - {"Content-Type", "application/x-www-form-urlencoded"}, + {"Content-Type", "application/json"}, {"Authorization", "DeepL-Auth-Key #{api_key()}"} ] ) do diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex index e4a309cea6..7aab05b367 100644 --- a/lib/pleroma/uploaders/local.ex +++ b/lib/pleroma/uploaders/local.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Uploaders.Local do [file | folders] -> path = Path.join([upload_path()] ++ Enum.reverse(folders)) - File.mkdir_p!(path) + Pleroma.Backports.mkdir_p!(path) {path, file} end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 427f7878d8..84551afd5a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -150,7 +150,7 @@ defmodule Pleroma.User do field(:allow_following_move, :boolean, default: true) field(:skip_thread_containment, :boolean, default: false) field(:actor_type, :string, default: "Person") - field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: []) + field(:also_known_as, {:array, ObjectValidators.BareUri}, default: []) field(:inbox, :string) field(:shared_inbox, :string) field(:accepts_chat_messages, :boolean, default: nil) diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 244b08adbd..3f67cdf0ce 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -193,7 +193,7 @@ defmodule Pleroma.User.Backup do backup = Repo.preload(backup, :user) tempfile = Path.join([backup.tempdir, backup.file_name]) - with {_, :ok} <- {:mkdir, File.mkdir_p(backup.tempdir)}, + with {_, :ok} <- {:mkdir, Pleroma.Backports.mkdir_p(backup.tempdir)}, {_, :ok} <- {:actor, actor(backup.tempdir, backup.user)}, {_, :ok} <- {:statuses, statuses(backup.tempdir, backup.user)}, {_, :ok} <- {:likes, likes(backup.tempdir, backup.user)}, diff --git a/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex b/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex new file mode 100644 index 0000000000..b3eb0b3909 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do + @moduledoc """ + QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread. + """ + require Pleroma.Constants + + alias Pleroma.User + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + @impl true + def history_awareness, do: :auto + + @impl true + def filter( + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "object" => %{ + "actor" => actor, + "type" => "Note", + "inReplyTo" => in_reply_to + } + } = activity + ) do + with true <- is_binary(in_reply_to), + true <- Pleroma.Constants.as_public() in to, + %User{follower_address: followers_collection, local: true} <- + User.get_by_ap_id(actor) do + updated_to = + [followers_collection | to] + |> Kernel.--([Pleroma.Constants.as_public()]) + + updated_cc = + [Pleroma.Constants.as_public() | cc] + |> Kernel.--([followers_collection]) + + updated_activity = + activity + |> Map.put("to", updated_to) + |> Map.put("cc", updated_cc) + |> put_in(["object", "to"], updated_to) + |> put_in(["object", "cc"], updated_cc) + + {:ok, updated_activity} + else + _ -> {:ok, activity} + end + end + + @impl true + def filter(activity), do: {:ok, activity} + + @impl true + def describe, do: {:ok, %{}} +end 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 49d17d8b95..54f0e6bc18 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -87,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do Path.join(Config.get([:instance, :static_dir]), "emoji/stolen") ) - File.mkdir_p(emoji_dir_path) + Pleroma.Backports.mkdir_p(emoji_dir_path) new_emojis = foreign_emojis diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 4a5cbd64e9..cf22c48999 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -93,7 +93,20 @@ defmodule Pleroma.Web.ActivityPub.Publisher do {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) - cc = Map.get(params, :cc, []) + param_cc = Map.get(params, :cc, []) + + original_cc = Map.get(data, "cc", []) + + public_address = Pleroma.Constants.as_public() + + # Ensure unlisted posts don't lose the public address in the cc + # if the param_cc was set + cc = + if public_address in original_cc and public_address not in param_cc do + [public_address | param_cc] + else + param_cc + end json = data diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 6517f5effa..8819e15965 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -664,6 +664,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + # Rewrite dislikes into the thumbs down emoji + defp handle_incoming_normalized(%{"type" => "Dislike"} = data, options) do + data + |> Map.put("type", "EmojiReact") + |> Map.put("content", "👎") + |> handle_incoming_normalized(options) + end + + defp handle_incoming_normalized( + %{"type" => "Undo", "object" => %{"type" => "Dislike"}} = data, + options + ) do + data + |> put_in(["object", "type"], "EmojiReact") + |> put_in(["object", "content"], "👎") + |> handle_incoming_normalized(options) + end + defp handle_incoming_normalized(_, _), do: :error @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil diff --git a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex index f595583b6b..6f77584a86 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_scrobble_operation.ex @@ -59,11 +59,15 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do album: %Schema{type: :string, description: "The album of the media playing"}, artist: %Schema{type: :string, description: "The artist of the media playing"}, length: %Schema{type: :integer, description: "The length of the media playing"}, - externalLink: %Schema{type: :string, description: "A URL referencing the media playing"}, + external_link: %Schema{type: :string, description: "A URL referencing the media playing"}, visibility: %Schema{ allOf: [VisibilityScope], default: "public", description: "Scrobble visibility" + }, + externalLink: %Schema{ + type: :string, + description: "Deprecated, use `external_link` instead" } }, example: %{ @@ -71,7 +75,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do "artist" => "Some Artist", "album" => "Some Album", "length" => 180_000, - "externalLink" => "https://www.last.fm/music/Some+Artist/_/Some+Title" + "external_link" => "https://www.last.fm/music/Some+Artist/_/Some+Title" } } end @@ -85,7 +89,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do title: %Schema{type: :string, description: "The title of the media playing"}, album: %Schema{type: :string, description: "The album of the media playing"}, artist: %Schema{type: :string, description: "The artist of the media playing"}, - externalLink: %Schema{type: :string, description: "A URL referencing the media playing"}, + external_link: %Schema{type: :string, description: "A URL referencing the media playing"}, length: %Schema{ type: :integer, description: "The length of the media playing", @@ -100,7 +104,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do "artist" => "Some Artist", "album" => "Some Album", "length" => 180_000, - "externalLink" => "https://www.last.fm/music/Some+Artist/_/Some+Title", + "external_link" => "https://www.last.fm/music/Some+Artist/_/Some+Title", "created_at" => "2019-09-28T12:40:45.000Z" } } diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index e69d421ee4..f60ed8b02b 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -91,7 +91,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp listen_object(draft) do object = draft.params - |> Map.take([:album, :artist, :title, :length, :externalLink]) + |> Map.take([:album, :artist, :title, :length]) + |> Map.put(:externalLink, Map.get(draft.params, :external_link)) |> Map.new(fn {key, value} -> {to_string(key), value} end) |> Map.put("type", "Audio") |> Map.put("to", draft.to) diff --git a/lib/pleroma/web/instance_document.ex b/lib/pleroma/web/instance_document.ex index 9da3c5008e..143a0b0b83 100644 --- a/lib/pleroma/web/instance_document.ex +++ b/lib/pleroma/web/instance_document.ex @@ -46,7 +46,7 @@ defmodule Pleroma.Web.InstanceDocument do defp put_file(origin_path, destination_path) do with destination <- instance_static_dir(destination_path), - {_, :ok} <- {:mkdir_p, File.mkdir_p(Path.dirname(destination))}, + {_, :ok} <- {:mkdir_p, Pleroma.Backports.mkdir_p(Path.dirname(destination))}, {_, {:ok, _}} <- {:copy, File.copy(origin_path, destination)} do :ok else diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index 628aa311b5..d9a1ba41ea 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -190,7 +190,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do f.() rescue error -> - Logger.error("#{__MODULE__} search error: #{inspect(error)}") + Logger.error(Exception.format(:error, error, __STACKTRACE__)) fallback 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 5894c764b6..1b6f26af72 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -287,7 +287,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do birthday_required: Config.get([:instance, :birthday_required]), birthday_min_age: Config.get([:instance, :birthday_min_age]), translation: supported_languages(), - base_urls: base_urls + base_urls: base_urls, + markup: markup() }, stats: %{mau: Pleroma.User.active_user_count()}, vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) @@ -338,4 +339,12 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do target_languages: target_languages } end + + defp markup do + %{ + allow_inline_images: Config.get([:markup, :allow_inline_images]), + allow_headings: Config.get([:markup, :allow_headings]), + allow_tables: Config.get([:markup, :allow_tables]) + } + end end diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex index bf6dc500c0..5f5f7643f7 100644 --- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex @@ -24,6 +24,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation def create(%{assigns: %{user: user}, body_params: params} = conn, _) do + params = + params + |> Map.put_new(:external_link, Map.get(params, :externalLink)) + with {:ok, activity} <- CommonAPI.listen(user, params) do render(conn, "show.json", activity: activity, for: user) else diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex index edf0a23903..51828ad975 100644 --- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex +++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex @@ -27,8 +27,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do title: object.data["title"] |> HTML.strip_tags(), artist: object.data["artist"] |> HTML.strip_tags(), album: object.data["album"] |> HTML.strip_tags(), - externalLink: object.data["externalLink"], - length: object.data["length"] + external_link: object.data["externalLink"], + length: object.data["length"], + # DEPRECATED + externalLink: object.data["externalLink"] } end diff --git a/lib/pleroma/workers/delete_worker.ex b/lib/pleroma/workers/delete_worker.ex index 6a1c7bb383..4f52edd28b 100644 --- a/lib/pleroma/workers/delete_worker.ex +++ b/lib/pleroma/workers/delete_worker.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.DeleteWorker do - alias Pleroma.Instances.Instance alias Pleroma.User use Oban.Worker, queue: :slow @@ -15,7 +14,15 @@ defmodule Pleroma.Workers.DeleteWorker do end def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do - Instance.perform(:delete_instance, host) + Pleroma.Repo.transaction(fn -> + User.Query.build(%{nickname: "@#{host}"}) + |> Pleroma.Repo.all() + |> Enum.each(fn user -> + %{"op" => "delete_user", "user_id" => user.id} + |> __MODULE__.new() + |> Oban.insert() + end) + end) end @impl true diff --git a/mix.exs b/mix.exs index e34ee0cbc9..971084f942 100644 --- a/mix.exs +++ b/mix.exs @@ -37,22 +37,13 @@ defmodule Pleroma.Mixfile do pleroma: [ include_executables_for: [:unix], applications: [ex_syslogger: :load, syslog: :load, eldap: :transient], - steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1], + steps: [:assemble, ©_files/1, ©_nginx_config/1], config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}] ] ] ] end - def put_otp_version(%{path: target_path} = release) do - File.write!( - Path.join([target_path, "OTP_VERSION"]), - Pleroma.OTPVersion.version() - ) - - release - end - def copy_files(%{path: target_path} = release) do File.cp_r!("./rel/files", target_path) release diff --git a/priv/repo/migrations/20240904142434_assign_app_user.exs b/priv/repo/migrations/20240904142434_assign_app_user.exs index 11bec529bf..74740220d6 100644 --- a/priv/repo/migrations/20240904142434_assign_app_user.exs +++ b/priv/repo/migrations/20240904142434_assign_app_user.exs @@ -1,20 +1,24 @@ defmodule Pleroma.Repo.Migrations.AssignAppUser do use Ecto.Migration + import Ecto.Query + alias Pleroma.Repo alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Token def up do - Repo.all(Token) - |> Enum.group_by(fn x -> Map.get(x, :app_id) end) - |> Enum.each(fn {_app_id, tokens} -> - token = - Enum.filter(tokens, fn x -> not is_nil(x.user_id) end) - |> List.first() - + Token + |> where([t], not is_nil(t.user_id)) + |> group_by([t], t.app_id) + |> select([t], %{app_id: t.app_id, id: min(t.id)}) + |> order_by(asc: :app_id) + |> Repo.stream() + |> Stream.each(fn %{id: id} -> + token = Token.Query.get_by_id(id) |> Repo.one() App.maybe_update_owner(token) end) + |> Stream.run() end def down, do: :ok diff --git a/test/fixtures/friendica-dislike-undo.json b/test/fixtures/friendica-dislike-undo.json new file mode 100644 index 0000000000..b258e00be1 --- /dev/null +++ b/test/fixtures/friendica-dislike-undo.json @@ -0,0 +1,76 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "Hashtag": "as:Hashtag", + "PropertyValue": "schema:PropertyValue", + "conversation": "ostatus:conversation", + "dfrn": "http://purl.org/macgirvin/dfrn/1.0/", + "diaspora": "https://diasporafoundation.org/ns/", + "directMessage": "litepub:directMessage", + "discoverable": "toot:discoverable", + "featured": { + "@id": "toot:featured", + "@type": "@id" + }, + "litepub": "http://litepub.social/ns#", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "ostatus": "http://ostatus.org#", + "quoteUrl": "as:quoteUrl", + "schema": "http://schema.org#", + "sensitive": "as:sensitive", + "toot": "http://joinmastodon.org/ns#", + "value": "schema:value", + "vcard": "http://www.w3.org/2006/vcard/ns#" + } + ], + "actor": "https://my-place.social/profile/vaartis", + "cc": [ + "https://my-place.social/followers/vaartis" + ], + "id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182/Undo", + "instrument": { + "id": "https://my-place.social/friendica", + "name": "Friendica 'Interrupted Fern' 2024.12-1576", + "type": "Application", + "url": "https://my-place.social" + }, + "object": { + "actor": "https://my-place.social/profile/vaartis", + "cc": [ + "https://my-place.social/followers/vaartis" + ], + "diaspora:guid": "e599373b-1968-4b20-cd24-80d340160302", + "diaspora:like": "{\"author\":\"vaartis@my-place.social\",\"guid\":\"e599373b-1968-4b20-cd24-80d340160302\",\"parent_guid\":\"cd36feba-c31f3ed3fd5c064a-17c31593\",\"parent_type\":\"Post\",\"positive\":\"false\",\"author_signature\":\"xR2zLJNfc9Nhx1n8LLMWM1kde12my4cqamIsrH\\/UntKzuDwO4DuHBL0fkFhgC\\/dylxm4HqsHD45MQbtwQCVGq6WhC96TrbMuYEK61HIO23dTr3m+qJVtfdH4wyhUNHgiiYPhZpkLDfnR1JiRWmFTlmZC8q8JEkOB5IQsrWia2eOR6IsqDcdKO\\/Urgns9\\/BdQi8KnchBKSEFc1iUtcOEruvhunKGyW5zI\\/Rltfdz3xGH8tlw+YlMXeWXPnqgOJ9GzNA0lwG4U421L6yylYagW7oxIznnBLB4bO46vYZbgXZV1hiI9ZyveHOinLMY1QkmTj5CNvnx3\\/VJwLovd0v+0Nr2vu\\/3ftbpBXc6L1bsNjlRqtsfwJlcgl+tH1DC4W8tKf+Y3tdtzVw0CHXCuacxHLyq5wZd\\/5YfYR9SJQ\\/jInU4PHA5+hIE3PGqNUp5QfFE0umq56H7MQKsIPgM5mMV4fPAA8OpltuMVDvQYUxalrnvoTf00k90x1wCTK71\\/jQGh7r7PmGvSdfPr+65eVTjITD8\\/lTGIb8850v1fl3\\/i2R8Dv17jQIRyX5o9MXPSO6jHo4Swma5RzPA\\/0bRj6qRTyPkM1L9qEIr+2H2I7KKhT2ZE5GhAU7yI9A3VLBWzpTrUPMGbfpd1OjVTEqXAdMjpLDYI3Mh5zQ58p8xCzt+W+t0=\"}", + "id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182", + "instrument": { + "id": "https://my-place.social/friendica", + "name": "Friendica 'Interrupted Fern' 2024.12-1576", + "type": "Application", + "url": "https://my-place.social" + }, + "object": "https://pl.kotobank.ch/objects/301bce65-8a1b-4c49-a65c-fe2ce861a213", + "published": "2025-06-12T18:47:41Z", + "to": [ + "https://pl.kotobank.ch/users/vaartis", + "https://mitra.social/users/silverpill", + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Dislike" + }, + "published": "2025-06-12T18:41:25Z", + "signature": { + "created": "2025-06-12T18:44:16Z", + "creator": "https://my-place.social/profile/vaartis#main-key", + "nonce": "2d67847d4bd4b1b83a30d61eac6cdc7ad6b980df06a8b9b97217e1d8f7b6cf20", + "signatureValue": "LnoRMZuQGDvTICkShGBq28ynaj2lF1bViJFGS6n4gKn3IbxPWATHxao43gxWRc+HCTrHNg7quzgaW4+PYM7UVUz3jO+bjNKsN845nijOVdyFrPOXbuaij3KQh2OoHhFJWoV/ZQQTFF0kRK1qT4BwG+P8NqOOKAMv+Cw7ruQH+f2w7uDgcNIbCD1gLcwb6cw7WVe5qu8yMkKqp2kBdqW3RCsI85RmmFgwehDgH5nrX7ER1qbeLWrqy7echwD9/fO3rqAu13xDNyiGZHDT7JB3RUt0AyMm0XCfjbwSQ0n+MkYXgE4asvFz81+iiPCLt+6gePWAFc5odF1FxdySBpSuUOs4p92NzP9OhQ0c0qrqrzYI7aYklY7oMfxjkva+X+0bm3up+2IRJdnZa/pXlmwdcqTpyMr1sgzaexMUNBp3dq7zA51eEaakLDX3i2onXJowfmze3+6XgPAFHYamR+pRNtuEoY4uyYEK3fj5GgwJ4RtFJMYVoEs/Q8h3OgYRcK1FE9UlDjSqbQ7QIRn2Ib4wjgmkeM0vrHIwh/1CtqA/M/6WuYFzCaJBc8O9ykpK9ZMbw64ToQXKf2SqhZsDoyTWRWTO1PXOk1XCAAElUh8/WCyeghvgqLXn0LHov4lmBsHA5iMUcLqBKD3GJIHd+ExrOFxMZs4mBLLGyz0p5joJ3NY=", + "type": "RsaSignature2017" + }, + "to": [ + "https://pl.kotobank.ch/users/vaartis", + "https://mitra.social/users/silverpill", + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Undo" +} diff --git a/test/fixtures/friendica-dislike.json b/test/fixtures/friendica-dislike.json new file mode 100644 index 0000000000..c75939073b --- /dev/null +++ b/test/fixtures/friendica-dislike.json @@ -0,0 +1,56 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "Hashtag": "as:Hashtag", + "PropertyValue": "schema:PropertyValue", + "conversation": "ostatus:conversation", + "dfrn": "http://purl.org/macgirvin/dfrn/1.0/", + "diaspora": "https://diasporafoundation.org/ns/", + "directMessage": "litepub:directMessage", + "discoverable": "toot:discoverable", + "featured": { + "@id": "toot:featured", + "@type": "@id" + }, + "litepub": "http://litepub.social/ns#", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "ostatus": "http://ostatus.org#", + "quoteUrl": "as:quoteUrl", + "schema": "http://schema.org#", + "sensitive": "as:sensitive", + "toot": "http://joinmastodon.org/ns#", + "value": "schema:value", + "vcard": "http://www.w3.org/2006/vcard/ns#" + } + ], + "actor": "https://my-place.social/profile/vaartis", + "cc": [ + "https://my-place.social/followers/vaartis" + ], + "diaspora:guid": "e599373b-1968-4b20-cd24-80d340160302", + "diaspora:like": "{\"author\":\"vaartis@my-place.social\",\"guid\":\"e599373b-1968-4b20-cd24-80d340160302\",\"parent_guid\":\"cd36feba-c31f3ed3fd5c064a-17c31593\",\"parent_type\":\"Post\",\"positive\":\"false\",\"author_signature\":\"xR2zLJNfc9Nhx1n8LLMWM1kde12my4cqamIsrH\\/UntKzuDwO4DuHBL0fkFhgC\\/dylxm4HqsHD45MQbtwQCVGq6WhC96TrbMuYEK61HIO23dTr3m+qJVtfdH4wyhUNHgiiYPhZpkLDfnR1JiRWmFTlmZC8q8JEkOB5IQsrWia2eOR6IsqDcdKO\\/Urgns9\\/BdQi8KnchBKSEFc1iUtcOEruvhunKGyW5zI\\/Rltfdz3xGH8tlw+YlMXeWXPnqgOJ9GzNA0lwG4U421L6yylYagW7oxIznnBLB4bO46vYZbgXZV1hiI9ZyveHOinLMY1QkmTj5CNvnx3\\/VJwLovd0v+0Nr2vu\\/3ftbpBXc6L1bsNjlRqtsfwJlcgl+tH1DC4W8tKf+Y3tdtzVw0CHXCuacxHLyq5wZd\\/5YfYR9SJQ\\/jInU4PHA5+hIE3PGqNUp5QfFE0umq56H7MQKsIPgM5mMV4fPAA8OpltuMVDvQYUxalrnvoTf00k90x1wCTK71\\/jQGh7r7PmGvSdfPr+65eVTjITD8\\/lTGIb8850v1fl3\\/i2R8Dv17jQIRyX5o9MXPSO6jHo4Swma5RzPA\\/0bRj6qRTyPkM1L9qEIr+2H2I7KKhT2ZE5GhAU7yI9A3VLBWzpTrUPMGbfpd1OjVTEqXAdMjpLDYI3Mh5zQ58p8xCzt+W+t0=\"}", + "id": "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182", + "instrument": { + "id": "https://my-place.social/friendica", + "name": "Friendica 'Interrupted Fern' 2024.12-1576", + "type": "Application", + "url": "https://my-place.social" + }, + "object": "https://pl.kotobank.ch/objects/301bce65-8a1b-4c49-a65c-fe2ce861a213", + "published": "2025-06-12T18:47:41Z", + "signature": { + "created": "2025-06-12T18:47:42Z", + "creator": "https://my-place.social/profile/vaartis#main-key", + "nonce": "84e496f80b09d7a299c5cc89e8cadd13abf621b3a0a321684fa74278b68a6dd8", + "signatureValue": "qe2WxY+j7daIYLRadCctgal6A1s9XgoiMfM/8KjJm15w0sSizYYqruyQ5gS44e+cj5GHc9v5gP2ieod5v7eHAPzlcDI4bfkcyHVapAXTqU67ZebW+v6Q+21IMDgqrkYCv5TbV7LTxltW59dlqovpHE4TEe/M7xLKWJ3vVchRUcWqH9kDmak0nacoqYVAb5E9jYnQhUWPTCfPV82qQpeWQPOZ4iIvPw6rDkSSY5jL6bCogBZblHGpUjXfe/FPlacaCWiTQdoga3yOBXB1RYPw9nh5FI5Xkv/oi+52WmJrECinlD6AL8/BpiYvKz236zy7p/TR4BXlCx9RR/msjOnSabkQ4kmYFrRr80UDCGF+CdkdzLl8K9rSE3PbF1+nEqD7X0GOWn/DdtixqXJw6IR4bh32YW2SlcrSRBvI1p82Mv68BeqRaYqL6FAhKFwLhX5JpXngZ3k0g7rWWxc498voPWnFZDyCTRNxO9VIIUavDDEQ0BdFk6WDb8zx9tsAg8JoK57eVDcFly7tfVQffYiHpve06d8ag1DtzipqguRsURmuqpGNMq28XBTxwtrP2LnXXHKxoYN/YQ9cDnCKclbx7/uKmOVMLkLZlM0wAVoZpm5z2fG4voKqFiGZ1PoiFY2sN4URMArJtygV3PlTX4ASAQrak0ksvEo9msrBUD0Su9c=", + "type": "RsaSignature2017" + }, + "to": [ + "https://pl.kotobank.ch/users/vaartis", + "https://mitra.social/users/silverpill", + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Dislike" +} diff --git a/test/mix/tasks/pleroma/frontend_test.exs b/test/mix/tasks/pleroma/frontend_test.exs index 6d09f8e369..59ebcec92b 100644 --- a/test/mix/tasks/pleroma/frontend_test.exs +++ b/test/mix/tasks/pleroma/frontend_test.exs @@ -11,7 +11,7 @@ defmodule Mix.Tasks.Pleroma.FrontendTest do @dir "test/frontend_static_test" setup do - File.mkdir_p!(@dir) + Pleroma.Backports.mkdir_p!(@dir) clear_config([:instance, :static_dir], @dir) on_exit(fn -> @@ -50,7 +50,7 @@ defmodule Mix.Tasks.Pleroma.FrontendTest do folder = Path.join([@dir, "frontends", "pleroma", "fantasy"]) previously_existing = Path.join([folder, "temp"]) - File.mkdir_p!(folder) + Pleroma.Backports.mkdir_p!(folder) File.write!(previously_existing, "yey") assert File.exists?(previously_existing) diff --git a/test/mix/tasks/pleroma/instance_test.exs b/test/mix/tasks/pleroma/instance_test.exs index b1c10e03c9..5ecb6e445b 100644 --- a/test/mix/tasks/pleroma/instance_test.exs +++ b/test/mix/tasks/pleroma/instance_test.exs @@ -7,7 +7,7 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do use Pleroma.DataCase setup do - File.mkdir_p!(tmp_path()) + Pleroma.Backports.mkdir_p!(tmp_path()) on_exit(fn -> File.rm_rf(tmp_path()) diff --git a/test/mix/tasks/pleroma/uploads_test.exs b/test/mix/tasks/pleroma/uploads_test.exs index f3d5aa64fd..0aa24807ed 100644 --- a/test/mix/tasks/pleroma/uploads_test.exs +++ b/test/mix/tasks/pleroma/uploads_test.exs @@ -62,7 +62,7 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do upload_dir = Config.get([Pleroma.Uploaders.Local, :uploads]) if not File.exists?(upload_dir) || File.ls!(upload_dir) == [] do - File.mkdir_p(upload_dir) + Pleroma.Backports.mkdir_p(upload_dir) Path.join([upload_dir, "file.txt"]) |> File.touch() diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs index 6ab3e657e5..b458401a73 100644 --- a/test/pleroma/emoji/pack_test.exs +++ b/test/pleroma/emoji/pack_test.exs @@ -58,7 +58,7 @@ defmodule Pleroma.Emoji.PackTest do test "skips existing emojis when adding from zip file", %{pack: pack} do # First, let's create a test pack with a "bear" emoji test_pack_path = Path.join(@emoji_path, "test_bear_pack") - File.mkdir_p(test_pack_path) + Pleroma.Backports.mkdir_p(test_pack_path) # Create a pack.json file File.write!(Path.join(test_pack_path, "pack.json"), """ diff --git a/test/pleroma/frontend_test.exs b/test/pleroma/frontend_test.exs index c89c56c8c1..22e0ffb9ab 100644 --- a/test/pleroma/frontend_test.exs +++ b/test/pleroma/frontend_test.exs @@ -9,7 +9,7 @@ defmodule Pleroma.FrontendTest do @dir "test/frontend_static_test" setup do - File.mkdir_p!(@dir) + Pleroma.Backports.mkdir_p!(@dir) clear_config([:instance, :static_dir], @dir) on_exit(fn -> @@ -46,7 +46,7 @@ defmodule Pleroma.FrontendTest do folder = Path.join([@dir, "frontends", "pleroma", "fantasy"]) previously_existing = Path.join([folder, "temp"]) - File.mkdir_p!(folder) + Pleroma.Backports.mkdir_p!(folder) File.write!(previously_existing, "yey") assert File.exists?(previously_existing) diff --git a/test/pleroma/instances/instance_test.exs b/test/pleroma/instances/instance_test.exs index f195d9bd6a..ed536c55c7 100644 --- a/test/pleroma/instances/instance_test.exs +++ b/test/pleroma/instances/instance_test.exs @@ -5,9 +5,8 @@ defmodule Pleroma.Instances.InstanceTest do alias Pleroma.Instances.Instance alias Pleroma.Repo - alias Pleroma.Tests.ObanHelpers - alias Pleroma.Web.CommonAPI + use Oban.Testing, repo: Pleroma.Repo use Pleroma.DataCase import ExUnit.CaptureLog @@ -214,32 +213,14 @@ defmodule Pleroma.Instances.InstanceTest do end end - test "delete_users_and_activities/1 deletes remote instance users and activities" do - [mario, luigi, _peach, wario] = - users = [ - insert(:user, nickname: "mario@mushroom.kingdom", name: "Mario"), - insert(:user, nickname: "luigi@mushroom.kingdom", name: "Luigi"), - insert(:user, nickname: "peach@mushroom.kingdom", name: "Peach"), - insert(:user, nickname: "wario@greedville.biz", name: "Wario") - ] + test "delete_users_and_activities/1 schedules a job to delete the instance and users" do + insert(:user, nickname: "mario@mushroom.kingdom", name: "Mario") - {:ok, post1} = CommonAPI.post(mario, %{status: "letsa go!"}) - {:ok, post2} = CommonAPI.post(luigi, %{status: "itsa me... luigi"}) - {:ok, post3} = CommonAPI.post(wario, %{status: "WHA-HA-HA!"}) + {:ok, _job} = Instance.delete_users_and_activities("mushroom.kingdom") - {:ok, job} = Instance.delete_users_and_activities("mushroom.kingdom") - :ok = ObanHelpers.perform(job) - - [mario, luigi, peach, wario] = Repo.reload(users) - - refute mario.is_active - refute luigi.is_active - refute peach.is_active - refute peach.name == "Peach" - - assert wario.is_active - assert wario.name == "Wario" - - assert [nil, nil, %{}] = Repo.reload([post1, post2, post3]) + assert_enqueued( + worker: Pleroma.Workers.DeleteWorker, + args: %{"op" => "delete_instance", "host" => "mushroom.kingdom"} + ) end end diff --git a/test/pleroma/object_test.exs b/test/pleroma/object_test.exs index ed5c2b6c80..13e941e4db 100644 --- a/test/pleroma/object_test.exs +++ b/test/pleroma/object_test.exs @@ -156,7 +156,7 @@ defmodule Pleroma.ObjectTest do uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) - File.mkdir_p!(uploads_dir) + Pleroma.Backports.mkdir_p!(uploads_dir) file = %Plug.Upload{ content_type: "image/jpeg", diff --git a/test/pleroma/safe_zip_test.exs b/test/pleroma/safe_zip_test.exs index 3312d4e633..f07b25675b 100644 --- a/test/pleroma/safe_zip_test.exs +++ b/test/pleroma/safe_zip_test.exs @@ -9,12 +9,12 @@ defmodule Pleroma.SafeZipTest do setup do # Ensure tmp directory exists - File.mkdir_p!(@tmp_dir) + Pleroma.Backports.mkdir_p!(@tmp_dir) on_exit(fn -> # Clean up any files created during tests File.rm_rf!(@tmp_dir) - File.mkdir_p!(@tmp_dir) + Pleroma.Backports.mkdir_p!(@tmp_dir) end) :ok @@ -89,7 +89,7 @@ defmodule Pleroma.SafeZipTest do # For this test, we'll manually check if the file exists in the archive # by extracting it and verifying it exists extract_dir = Path.join(@tmp_dir, "extract_check") - File.mkdir_p!(extract_dir) + Pleroma.Backports.mkdir_p!(extract_dir) {:ok, files} = SafeZip.unzip_file(zip_path, extract_dir) # Verify the root file was extracted @@ -145,7 +145,7 @@ defmodule Pleroma.SafeZipTest do test "can create zip with directories" do # Create a directory structure dir_path = Path.join(@tmp_dir, "test_dir") - File.mkdir_p!(dir_path) + Pleroma.Backports.mkdir_p!(dir_path) file_in_dir_path = Path.join(dir_path, "file_in_dir.txt") File.write!(file_in_dir_path, "file in directory") @@ -428,7 +428,7 @@ defmodule Pleroma.SafeZipTest do # Create a directory and a file in it dir_path = Path.join(@tmp_dir, "file_in_dir") - File.mkdir_p!(dir_path) + Pleroma.Backports.mkdir_p!(dir_path) file_in_dir_path = Path.join(dir_path, "test_file.txt") File.write!(file_in_dir_path, "file in directory content") diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 44e2d0d65d..0b4dc9197e 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -2792,6 +2792,15 @@ defmodule Pleroma.UserTest do assert user_updated.also_known_as |> length() == 1 assert user2.ap_id in user_updated.also_known_as end + + test "should tolerate non-http(s) aliases" do + user = + insert(:user, %{ + also_known_as: ["at://did:plc:xgvzy7ni6ig6ievcbls5jaxe"] + }) + + assert "at://did:plc:xgvzy7ni6ig6ievcbls5jaxe" in user.also_known_as + end end describe "alias_users/1" do diff --git a/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs b/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs new file mode 100644 index 0000000000..f66383bf5e --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs @@ -0,0 +1,139 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.QuietReplyTest do + use Pleroma.DataCase + import Pleroma.Factory + + require Pleroma.Constants + + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.MRF.QuietReply + alias Pleroma.Web.CommonAPI + + test "replying to public post is forced to be quiet" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [ + batman.ap_id, + Pleroma.Constants.as_public() + ], + "cc" => [robin.follower_address], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [ + batman.ap_id, + Pleroma.Constants.as_public() + ], + "cc" => [robin.follower_address], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert batman.ap_id in filtered["to"] + assert batman.ap_id in filtered["object"]["to"] + assert robin.follower_address in filtered["to"] + assert robin.follower_address in filtered["object"]["to"] + assert Pleroma.Constants.as_public() in filtered["cc"] + assert Pleroma.Constants.as_public() in filtered["object"]["cc"] + end + + test "replying to unlisted post is unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!", visibility: "private"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [batman.ap_id], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "replying direct is unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [batman.ap_id], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "replying followers-only is unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "non-reply posts are unmodified" do + batman = insert(:user, nickname: "batman") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + assert {:ok, filtered} = QuietReply.filter(post) + + assert match?(^filtered, post) + end +end diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index a6f25c9a72..44e81377ed 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -409,4 +409,105 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert decoded["cc"] == [] end + + test "unlisted activities retain public address in cc" do + user = insert(:user) + + # simulate unlistd activity by only having + # public address in cc + activity = + insert(:note_activity, + user: user, + data_attrs: %{ + "cc" => [@as_public], + "to" => [user.follower_address] + } + ) + + assert @as_public in activity.data["cc"] + + prepared = + Publisher.prepare_one(%{ + inbox: "https://remote.instance/users/someone/inbox", + activity_id: activity.id + }) + + {:ok, decoded} = Jason.decode(prepared.json) + + assert @as_public in decoded["cc"] + + # maybe we also have another inbox in cc + # during Publishing + activity = + insert(:note_activity, + user: user, + data_attrs: %{ + "cc" => [@as_public], + "to" => [user.follower_address] + } + ) + + prepared = + Publisher.prepare_one(%{ + inbox: "https://remote.instance/users/someone/inbox", + activity_id: activity.id, + cc: ["https://remote.instance/users/someone_else/inbox"] + }) + + {:ok, decoded} = Jason.decode(prepared.json) + + assert decoded["cc"] == [@as_public, "https://remote.instance/users/someone_else/inbox"] + end + + test "public address in cc parameter is preserved" do + user = insert(:user) + + cc_with_public = [@as_public, "https://example.org/users/other"] + + activity = + insert(:note_activity, + user: user, + data_attrs: %{ + "cc" => cc_with_public, + "to" => [user.follower_address] + } + ) + + assert @as_public in activity.data["cc"] + + prepared = + Publisher.prepare_one(%{ + inbox: "https://remote.instance/users/someone/inbox", + activity_id: activity.id, + cc: cc_with_public + }) + + {:ok, decoded} = Jason.decode(prepared.json) + + assert cc_with_public == decoded["cc"] + end + + test "cc parameter is preserved" do + user = insert(:user) + + activity = + insert(:note_activity, + user: user, + data_attrs: %{ + "cc" => ["https://example.com/specific/user"], + "to" => [user.follower_address] + } + ) + + prepared = + Publisher.prepare_one(%{ + inbox: "https://remote.instance/users/someone/inbox", + activity_id: activity.id, + cc: ["https://example.com/specific/user"] + }) + + {:ok, decoded} = Jason.decode(prepared.json) + + assert decoded["cc"] == ["https://example.com/specific/user"] + end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs index fc04c13917..27f8522ce6 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs @@ -143,4 +143,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do assert {:ok, activity} = Transmogrifier.handle_incoming(data) assert activity.data["type"] == "Like" end + + test "it changes incoming dislikes into emoji reactions" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/friendica-dislike.json") + |> Jason.decode!() + |> Map.put("object", activity.data["object"]) + + _actor = insert(:user, ap_id: data["actor"], local: false) + + {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) + + refute Enum.empty?(activity.recipients) + + assert data["actor"] == "https://my-place.social/profile/vaartis" + assert data["type"] == "EmojiReact" + assert data["content"] == "👎" + assert data["id"] == "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182" + assert data["object"] == activity.data["object"] + + data = + File.read!("test/fixtures/friendica-dislike-undo.json") + |> Jason.decode!() + |> put_in(["object", "object"], activity.data["object"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "https://my-place.social/profile/vaartis" + assert data["type"] == "Undo" + + assert data["object"] == + "https://my-place.social/objects/e599373b-1368-4b20-cd24-837166957182" + end end diff --git a/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs b/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs index 0d1a4999eb..a6b8dba465 100644 --- a/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/frontend_controller_test.exs @@ -13,7 +13,7 @@ defmodule Pleroma.Web.AdminAPI.FrontendControllerTest do setup do clear_config([:instance, :static_dir], @dir) - File.mkdir_p!(Pleroma.Frontend.dir()) + Pleroma.Backports.mkdir_p!(Pleroma.Frontend.dir()) on_exit(fn -> File.rm_rf(@dir) diff --git a/test/pleroma/web/admin_api/controllers/instance_controller_test.exs b/test/pleroma/web/admin_api/controllers/instance_controller_test.exs index 6cca623f35..5adcd069d3 100644 --- a/test/pleroma/web/admin_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/instance_controller_test.exs @@ -8,8 +8,6 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do import Pleroma.Factory - alias Pleroma.Repo - alias Pleroma.Tests.ObanHelpers alias Pleroma.Web.CommonAPI setup_all do @@ -69,19 +67,19 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do test "DELETE /instances/:instance", %{conn: conn} do clear_config([:instance, :admin_privileges], [:instances_delete]) - user = insert(:user, nickname: "lain@lain.com") - post = insert(:note_activity, user: user) + insert(:user, nickname: "lain@lain.com") response = conn |> delete("/api/pleroma/admin/instances/lain.com") |> json_response(200) - [:ok] = ObanHelpers.perform_all() - assert response == "lain.com" - refute Repo.reload(user).is_active - refute Repo.reload(post) + + assert_enqueued( + worker: Pleroma.Workers.DeleteWorker, + args: %{"op" => "delete_instance", "host" => "lain.com"} + ) clear_config([:instance, :admin_privileges], []) diff --git a/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs b/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs index 9511dcceaa..344c908fe0 100644 --- a/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs @@ -10,7 +10,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentControllerTest do @default_instance_panel ~s(

Welcome to Pleroma!

) setup do - File.mkdir_p!(@dir) + Pleroma.Backports.mkdir_p!(@dir) on_exit(fn -> File.rm_rf(@dir) end) end diff --git a/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs index be94a02ade..bcc25b83e5 100644 --- a/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs @@ -19,10 +19,33 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do "artist" => "lain", "album" => "lain radio", "length" => "180000", - "externalLink" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1" + "external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1" }) - assert %{"title" => "lain radio episode 1"} = json_response_and_validate_schema(conn, 200) + assert %{ + "title" => "lain radio episode 1", + "external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1" + } = json_response_and_validate_schema(conn, 200) + end + + test "external_link fallback" do + %{conn: conn} = oauth_access(["write"]) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/pleroma/scrobble", %{ + "title" => "lain radio episode 2", + "artist" => "lain", + "album" => "lain radio", + "length" => "180000", + "externalLink" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2" + }) + + assert %{ + "title" => "lain radio episode 2", + "external_link" => "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2" + } = json_response_and_validate_schema(conn, 200) end end @@ -35,7 +58,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do title: "lain radio episode 1", artist: "lain", album: "lain radio", - externalLink: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1" + external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+1" }) {:ok, _activity} = @@ -43,7 +66,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do title: "lain radio episode 2", artist: "lain", album: "lain radio", - externalLink: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2" + external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+2" }) {:ok, _activity} = @@ -51,7 +74,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do title: "lain radio episode 3", artist: "lain", album: "lain radio", - externalLink: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+3" + external_link: "https://www.last.fm/music/lain/lain+radio/lain+radio+episode+3" }) conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles") diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs index 6f4d24d9e6..a7af3e74e1 100644 --- a/test/pleroma/web/plugs/frontend_static_plug_test.exs +++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs @@ -13,7 +13,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do @dir "test/tmp/instance_static" setup do - File.mkdir_p!(@dir) + Pleroma.Backports.mkdir_p!(@dir) on_exit(fn -> File.rm_rf(@dir) end) end @@ -38,7 +38,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do clear_config([:frontends, :primary], %{"name" => name, "ref" => ref}) path = "#{@dir}/frontends/#{name}/#{ref}" - File.mkdir_p!(path) + Pleroma.Backports.mkdir_p!(path) File.write!("#{path}/index.html", "from frontend plug") index = get(conn, "/") @@ -52,7 +52,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do clear_config([:frontends, :admin], %{"name" => name, "ref" => ref}) path = "#{@dir}/frontends/#{name}/#{ref}" - File.mkdir_p!(path) + Pleroma.Backports.mkdir_p!(path) File.write!("#{path}/index.html", "from frontend plug") index = get(conn, "/pleroma/admin/") @@ -67,7 +67,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do clear_config([:frontends, :primary], %{"name" => name, "ref" => ref}) path = "#{@dir}/frontends/#{name}/#{ref}" - File.mkdir_p!("#{path}/proxy/rr/ss") + Pleroma.Backports.mkdir_p!("#{path}/proxy/rr/ss") File.write!("#{path}/proxy/rr/ss/Ek7w8WPVcAApOvN.jpg:large", "FB image") ConfigMock diff --git a/test/pleroma/web/plugs/instance_static_test.exs b/test/pleroma/web/plugs/instance_static_test.exs index 33b74dcf0c..b5a5a33347 100644 --- a/test/pleroma/web/plugs/instance_static_test.exs +++ b/test/pleroma/web/plugs/instance_static_test.exs @@ -8,7 +8,7 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do @dir "test/tmp/instance_static" setup do - File.mkdir_p!(@dir) + Pleroma.Backports.mkdir_p!(@dir) on_exit(fn -> File.rm_rf(@dir) end) end @@ -34,7 +34,7 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do refute html_response(bundled_index, 200) == "from frontend plug" path = "#{@dir}/frontends/#{name}/#{ref}" - File.mkdir_p!(path) + Pleroma.Backports.mkdir_p!(path) File.write!("#{path}/index.html", "from frontend plug") index = get(conn, "/") diff --git a/test/pleroma/workers/delete_worker_test.exs b/test/pleroma/workers/delete_worker_test.exs new file mode 100644 index 0000000000..b914aaee28 --- /dev/null +++ b/test/pleroma/workers/delete_worker_test.exs @@ -0,0 +1,39 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.DeleteWorkerTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + alias Pleroma.Instances.Instance + alias Pleroma.Tests.ObanHelpers + alias Pleroma.Workers.DeleteWorker + + describe "instance deletion" do + test "creates individual Oban jobs for each user when deleting an instance" do + user1 = insert(:user, nickname: "alice@example.com", name: "Alice") + user2 = insert(:user, nickname: "bob@example.com", name: "Bob") + + {:ok, job} = Instance.delete_users_and_activities("example.com") + + assert_enqueued( + worker: DeleteWorker, + args: %{"op" => "delete_instance", "host" => "example.com"} + ) + + {:ok, :ok} = ObanHelpers.perform(job) + + delete_user_jobs = all_enqueued(worker: DeleteWorker, args: %{"op" => "delete_user"}) + + assert length(delete_user_jobs) == 2 + + user_ids = [user1.id, user2.id] + job_user_ids = Enum.map(delete_user_jobs, fn job -> job.args["user_id"] end) + + assert Enum.sort(user_ids) == Enum.sort(job_user_ids) + end + end +end