Merge remote-tracking branch 'origin/develop' into improved-reachability

This commit is contained in:
Mark Felder 2025-06-27 15:59:46 -07:00
commit e58ecd3234
62 changed files with 778 additions and 127 deletions

View file

@ -208,7 +208,7 @@ docs-deploy:
before_script: before_script:
- apk add curl - apk add curl
script: 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: review_app:
image: alpine:3.9 image: alpine:3.9
stage: deploy stage: deploy
@ -249,7 +249,7 @@ spec-deploy:
before_script: before_script:
- apk add curl - apk add curl
script: 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: stop_review_app:

View file

@ -0,0 +1 @@
Fix AssignAppUser migration OOM

View file

@ -0,0 +1 @@
Use JSON for DeepL API requests

View file

@ -0,0 +1 @@
Deleting an instance queues individual jobs for each user that needs to be deleted from the server.

View file

@ -0,0 +1 @@
Support Dislike activity, as sent by Mitra and Friendica, by changing it into a thumbs-down EmojiReact

View file

@ -0,0 +1 @@
Expose markup configuration in InstanceView

View file

@ -0,0 +1 @@
Added MRF.QuietReply which prevents replies to public posts from being published to the timelines

View file

@ -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

View file

@ -0,0 +1 @@
Relax alsoKnownAs requirements to just URI, not necessarily HTTP(S)

View file

@ -0,0 +1 @@
Change scrobble external link param name to use snake case

View file

@ -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

View file

@ -0,0 +1 @@
Allow Terms of Service panel behaviour to be configurable

View file

@ -306,6 +306,7 @@ config :pleroma, :frontend_configurations,
collapseMessageWithSubject: false, collapseMessageWithSubject: false,
disableChat: false, disableChat: false,
greentext: false, greentext: false,
embeddedToS: true,
hideFilteredStatuses: false, hideFilteredStatuses: false,
hideMutedPosts: false, hideMutedPosts: false,
hidePostStats: false, hidePostStats: false,

View file

@ -1261,6 +1261,7 @@ config :pleroma, :config_description, [
background: "/static/aurora_borealis.jpg", background: "/static/aurora_borealis.jpg",
collapseMessageWithSubject: false, collapseMessageWithSubject: false,
greentext: false, greentext: false,
embeddedToS: true,
hideFilteredStatuses: false, hideFilteredStatuses: false,
hideMutedPosts: false, hideMutedPosts: false,
hidePostStats: false, hidePostStats: false,
@ -1312,6 +1313,12 @@ config :pleroma, :config_description, [
type: :boolean, type: :boolean,
description: "Enables green text on lines prefixed with the > character" 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, key: :hideFilteredStatuses,
label: "Hide Filtered Statuses", label: "Hide Filtered Statuses",

View file

@ -671,6 +671,7 @@ Audio scrobbling in Pleroma is **deprecated**.
"artist": "Some Artist", "artist": "Some Artist",
"album": "Some Album", "album": "Some Album",
"length": 180000, "length": 180000,
"external_link": "https://www.last.fm/music/Some+Artist/_/Some+Title",
"created_at": "2019-09-28T12:40:45.000Z" "created_at": "2019-09-28T12:40:45.000Z"
} }
] ]

View file

@ -271,7 +271,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
[config_dir, psql_dir, static_dir, uploads_dir] [config_dir, psql_dir, static_dir, uploads_dir]
|> Enum.reject(&File.exists?/1) |> Enum.reject(&File.exists?/1)
|> Enum.each(fn dir -> |> Enum.each(fn dir ->
File.mkdir_p!(dir) Pleroma.Backports.mkdir_p!(dir)
File.chmod!(dir, 0o700) File.chmod!(dir, 0o700)
end) end)

View file

@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxt do
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
if !File.exists?(static_dir) do if !File.exists?(static_dir) do
File.mkdir_p!(static_dir) Pleroma.Backports.mkdir_p!(static_dir)
end end
robots_txt_path = Path.join(static_dir, "robots.txt") robots_txt_path = Path.join(static_dir, "robots.txt")

72
lib/pleroma/backports.ex Normal file
View file

@ -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]
# <https://github.com/elixir-lang/elixir/pull/14242>
# 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

View file

@ -100,6 +100,7 @@ defmodule Pleroma.Constants do
"Add", "Add",
"Remove", "Remove",
"Like", "Like",
"Dislike",
"Announce", "Announce",
"Undo", "Undo",
"Flag", "Flag",
@ -115,6 +116,7 @@ defmodule Pleroma.Constants do
"Flag", "Flag",
"Follow", "Follow",
"Like", "Like",
"Dislike",
"EmojiReact", "EmojiReact",
"Announce" "Announce"
] ]

View file

@ -488,7 +488,7 @@ defmodule Pleroma.Emoji.Pack do
with true <- String.contains?(file_path, "/"), with true <- String.contains?(file_path, "/"),
path <- Path.dirname(file_path), path <- Path.dirname(file_path),
false <- File.exists?(path) do false <- File.exists?(path) do
File.mkdir_p!(path) Pleroma.Backports.mkdir_p!(path)
end end
end end
@ -536,7 +536,7 @@ defmodule Pleroma.Emoji.Pack do
emoji_path = emoji_path() emoji_path = emoji_path()
# Create the directory first if it does not exist. This is probably the first request made # 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 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 {:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
{:ok, Enum.sort(results)} {:ok, Enum.sort(results)}
else else
@ -561,7 +561,7 @@ defmodule Pleroma.Emoji.Pack do
end end
defp unzip(archive, pack_info, remote_pack, local_pack) do 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) files = Enum.map(remote_pack["files"], fn {_, path} -> path end)
# Fallback cannot contain a pack.json file # Fallback cannot contain a pack.json file
files = if pack_info[:fallback], do: files, else: ["pack.json" | files] files = if pack_info[:fallback], do: files, else: ["pack.json" | files]

View file

@ -66,7 +66,7 @@ defmodule Pleroma.Frontend do
def unzip(zip, dest) do def unzip(zip, dest) do
File.rm_rf!(dest) File.rm_rf!(dest)
File.mkdir_p!(dest) Pleroma.Backports.mkdir_p!(dest)
case Pleroma.SafeZip.unzip_data(zip, dest) do case Pleroma.SafeZip.unzip_data(zip, dest) do
{:ok, _} -> :ok {:ok, _} -> :ok
@ -90,7 +90,7 @@ defmodule Pleroma.Frontend do
defp install_frontend(frontend_info, source, dest) do defp install_frontend(frontend_info, source, dest) do
from = frontend_info["build_dir"] || "dist" from = frontend_info["build_dir"] || "dist"
File.rm_rf!(dest) File.rm_rf!(dest)
File.mkdir_p!(dest) Pleroma.Backports.mkdir_p!(dest)
File.cp_r!(Path.join([source, from]), dest) File.cp_r!(Path.join([source, from]), dest)
:ok :ok
end end

View file

@ -9,7 +9,6 @@ defmodule Pleroma.Instances.Instance do
alias Pleroma.Instances.Instance alias Pleroma.Instances.Instance
alias Pleroma.Maps alias Pleroma.Maps
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Workers.DeleteWorker alias Pleroma.Workers.DeleteWorker
use Ecto.Schema use Ecto.Schema
@ -294,16 +293,4 @@ defmodule Pleroma.Instances.Instance do
DeleteWorker.new(%{"op" => "delete_instance", "host" => host}) DeleteWorker.new(%{"op" => "delete_instance", "host" => host})
|> Oban.insert() |> Oban.insert()
end 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 end

View file

@ -24,17 +24,15 @@ defmodule Pleroma.Language.Translation.Deepl do
|> URI.to_string() |> URI.to_string()
case Pleroma.HTTP.post( case Pleroma.HTTP.post(
endpoint <> endpoint,
"?" <> Jason.encode!(%{
URI.encode_query(%{ text: [content],
text: content, source_lang: source_language |> String.upcase(),
source_lang: source_language |> String.upcase(), target_lang: target_language,
target_lang: target_language, tag_handling: "html"
tag_handling: "html" }),
}),
"",
[ [
{"Content-Type", "application/x-www-form-urlencoded"}, {"Content-Type", "application/json"},
{"Authorization", "DeepL-Auth-Key #{api_key()}"} {"Authorization", "DeepL-Auth-Key #{api_key()}"}
] ]
) do ) do

View file

@ -19,7 +19,7 @@ defmodule Pleroma.Uploaders.Local do
[file | folders] -> [file | folders] ->
path = Path.join([upload_path()] ++ Enum.reverse(folders)) path = Path.join([upload_path()] ++ Enum.reverse(folders))
File.mkdir_p!(path) Pleroma.Backports.mkdir_p!(path)
{path, file} {path, file}
end end

View file

@ -150,7 +150,7 @@ defmodule Pleroma.User do
field(:allow_following_move, :boolean, default: true) field(:allow_following_move, :boolean, default: true)
field(:skip_thread_containment, :boolean, default: false) field(:skip_thread_containment, :boolean, default: false)
field(:actor_type, :string, default: "Person") 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(:inbox, :string)
field(:shared_inbox, :string) field(:shared_inbox, :string)
field(:accepts_chat_messages, :boolean, default: nil) field(:accepts_chat_messages, :boolean, default: nil)

View file

@ -193,7 +193,7 @@ defmodule Pleroma.User.Backup do
backup = Repo.preload(backup, :user) backup = Repo.preload(backup, :user)
tempfile = Path.join([backup.tempdir, backup.file_name]) 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} <- {:actor, actor(backup.tempdir, backup.user)},
{_, :ok} <- {:statuses, statuses(backup.tempdir, backup.user)}, {_, :ok} <- {:statuses, statuses(backup.tempdir, backup.user)},
{_, :ok} <- {:likes, likes(backup.tempdir, backup.user)}, {_, :ok} <- {:likes, likes(backup.tempdir, backup.user)},

View file

@ -0,0 +1,61 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2023 Pleroma Authors <https://pleroma.social/>
# 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

View file

@ -87,7 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
Path.join(Config.get([:instance, :static_dir]), "emoji/stolen") Path.join(Config.get([:instance, :static_dir]), "emoji/stolen")
) )
File.mkdir_p(emoji_dir_path) Pleroma.Backports.mkdir_p(emoji_dir_path)
new_emojis = new_emojis =
foreign_emojis foreign_emojis

View file

@ -93,7 +93,20 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data) {: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 = json =
data data

View file

@ -664,6 +664,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
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 defp handle_incoming_normalized(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil

View file

@ -59,11 +59,15 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
album: %Schema{type: :string, description: "The album 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"}, artist: %Schema{type: :string, description: "The artist of the media playing"},
length: %Schema{type: :integer, description: "The length 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{ visibility: %Schema{
allOf: [VisibilityScope], allOf: [VisibilityScope],
default: "public", default: "public",
description: "Scrobble visibility" description: "Scrobble visibility"
},
externalLink: %Schema{
type: :string,
description: "Deprecated, use `external_link` instead"
} }
}, },
example: %{ example: %{
@ -71,7 +75,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
"artist" => "Some Artist", "artist" => "Some Artist",
"album" => "Some Album", "album" => "Some Album",
"length" => 180_000, "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 end
@ -85,7 +89,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
title: %Schema{type: :string, description: "The title of the media playing"}, title: %Schema{type: :string, description: "The title of the media playing"},
album: %Schema{type: :string, description: "The album 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"}, 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{ length: %Schema{
type: :integer, type: :integer,
description: "The length of the media playing", description: "The length of the media playing",
@ -100,7 +104,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaScrobbleOperation do
"artist" => "Some Artist", "artist" => "Some Artist",
"album" => "Some Album", "album" => "Some Album",
"length" => 180_000, "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" "created_at" => "2019-09-28T12:40:45.000Z"
} }
} }

View file

@ -91,7 +91,8 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
defp listen_object(draft) do defp listen_object(draft) do
object = object =
draft.params 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.new(fn {key, value} -> {to_string(key), value} end)
|> Map.put("type", "Audio") |> Map.put("type", "Audio")
|> Map.put("to", draft.to) |> Map.put("to", draft.to)

View file

@ -46,7 +46,7 @@ defmodule Pleroma.Web.InstanceDocument do
defp put_file(origin_path, destination_path) do defp put_file(origin_path, destination_path) do
with destination <- instance_static_dir(destination_path), 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, _}} <- {:copy, File.copy(origin_path, destination)} do
:ok :ok
else else

View file

@ -190,7 +190,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
f.() f.()
rescue rescue
error -> error ->
Logger.error("#{__MODULE__} search error: #{inspect(error)}") Logger.error(Exception.format(:error, error, __STACKTRACE__))
fallback fallback
end end
end end

View file

@ -287,7 +287,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
birthday_required: Config.get([:instance, :birthday_required]), birthday_required: Config.get([:instance, :birthday_required]),
birthday_min_age: Config.get([:instance, :birthday_min_age]), birthday_min_age: Config.get([:instance, :birthday_min_age]),
translation: supported_languages(), translation: supported_languages(),
base_urls: base_urls base_urls: base_urls,
markup: markup()
}, },
stats: %{mau: Pleroma.User.active_user_count()}, stats: %{mau: Pleroma.User.active_user_count()},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) 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 target_languages: target_languages
} }
end 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 end

View file

@ -24,6 +24,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaScrobbleOperation
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do 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 with {:ok, activity} <- CommonAPI.listen(user, params) do
render(conn, "show.json", activity: activity, for: user) render(conn, "show.json", activity: activity, for: user)
else else

View file

@ -27,8 +27,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
title: object.data["title"] |> HTML.strip_tags(), title: object.data["title"] |> HTML.strip_tags(),
artist: object.data["artist"] |> HTML.strip_tags(), artist: object.data["artist"] |> HTML.strip_tags(),
album: object.data["album"] |> HTML.strip_tags(), album: object.data["album"] |> HTML.strip_tags(),
externalLink: object.data["externalLink"], external_link: object.data["externalLink"],
length: object.data["length"] length: object.data["length"],
# DEPRECATED
externalLink: object.data["externalLink"]
} }
end end

View file

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.DeleteWorker do defmodule Pleroma.Workers.DeleteWorker do
alias Pleroma.Instances.Instance
alias Pleroma.User alias Pleroma.User
use Oban.Worker, queue: :slow use Oban.Worker, queue: :slow
@ -15,7 +14,15 @@ defmodule Pleroma.Workers.DeleteWorker do
end end
def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do 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 end
@impl true @impl true

11
mix.exs
View file

@ -37,22 +37,13 @@ defmodule Pleroma.Mixfile do
pleroma: [ pleroma: [
include_executables_for: [:unix], include_executables_for: [:unix],
applications: [ex_syslogger: :load, syslog: :load, eldap: :transient], applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1], steps: [:assemble, &copy_files/1, &copy_nginx_config/1],
config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}] config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
] ]
] ]
] ]
end 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 def copy_files(%{path: target_path} = release) do
File.cp_r!("./rel/files", target_path) File.cp_r!("./rel/files", target_path)
release release

View file

@ -1,20 +1,24 @@
defmodule Pleroma.Repo.Migrations.AssignAppUser do defmodule Pleroma.Repo.Migrations.AssignAppUser do
use Ecto.Migration use Ecto.Migration
import Ecto.Query
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
def up do def up do
Repo.all(Token) Token
|> Enum.group_by(fn x -> Map.get(x, :app_id) end) |> where([t], not is_nil(t.user_id))
|> Enum.each(fn {_app_id, tokens} -> |> group_by([t], t.app_id)
token = |> select([t], %{app_id: t.app_id, id: min(t.id)})
Enum.filter(tokens, fn x -> not is_nil(x.user_id) end) |> order_by(asc: :app_id)
|> List.first() |> Repo.stream()
|> Stream.each(fn %{id: id} ->
token = Token.Query.get_by_id(id) |> Repo.one()
App.maybe_update_owner(token) App.maybe_update_owner(token)
end) end)
|> Stream.run()
end end
def down, do: :ok def down, do: :ok

View file

@ -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"
}

56
test/fixtures/friendica-dislike.json vendored Normal file
View file

@ -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"
}

View file

@ -11,7 +11,7 @@ defmodule Mix.Tasks.Pleroma.FrontendTest do
@dir "test/frontend_static_test" @dir "test/frontend_static_test"
setup do setup do
File.mkdir_p!(@dir) Pleroma.Backports.mkdir_p!(@dir)
clear_config([:instance, :static_dir], @dir) clear_config([:instance, :static_dir], @dir)
on_exit(fn -> on_exit(fn ->
@ -50,7 +50,7 @@ defmodule Mix.Tasks.Pleroma.FrontendTest do
folder = Path.join([@dir, "frontends", "pleroma", "fantasy"]) folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
previously_existing = Path.join([folder, "temp"]) previously_existing = Path.join([folder, "temp"])
File.mkdir_p!(folder) Pleroma.Backports.mkdir_p!(folder)
File.write!(previously_existing, "yey") File.write!(previously_existing, "yey")
assert File.exists?(previously_existing) assert File.exists?(previously_existing)

View file

@ -7,7 +7,7 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
use Pleroma.DataCase use Pleroma.DataCase
setup do setup do
File.mkdir_p!(tmp_path()) Pleroma.Backports.mkdir_p!(tmp_path())
on_exit(fn -> on_exit(fn ->
File.rm_rf(tmp_path()) File.rm_rf(tmp_path())

View file

@ -62,7 +62,7 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do
upload_dir = Config.get([Pleroma.Uploaders.Local, :uploads]) upload_dir = Config.get([Pleroma.Uploaders.Local, :uploads])
if not File.exists?(upload_dir) || File.ls!(upload_dir) == [] do 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"]) Path.join([upload_dir, "file.txt"])
|> File.touch() |> File.touch()

View file

@ -58,7 +58,7 @@ defmodule Pleroma.Emoji.PackTest do
test "skips existing emojis when adding from zip file", %{pack: pack} do test "skips existing emojis when adding from zip file", %{pack: pack} do
# First, let's create a test pack with a "bear" emoji # First, let's create a test pack with a "bear" emoji
test_pack_path = Path.join(@emoji_path, "test_bear_pack") 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 # Create a pack.json file
File.write!(Path.join(test_pack_path, "pack.json"), """ File.write!(Path.join(test_pack_path, "pack.json"), """

View file

@ -9,7 +9,7 @@ defmodule Pleroma.FrontendTest do
@dir "test/frontend_static_test" @dir "test/frontend_static_test"
setup do setup do
File.mkdir_p!(@dir) Pleroma.Backports.mkdir_p!(@dir)
clear_config([:instance, :static_dir], @dir) clear_config([:instance, :static_dir], @dir)
on_exit(fn -> on_exit(fn ->
@ -46,7 +46,7 @@ defmodule Pleroma.FrontendTest do
folder = Path.join([@dir, "frontends", "pleroma", "fantasy"]) folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
previously_existing = Path.join([folder, "temp"]) previously_existing = Path.join([folder, "temp"])
File.mkdir_p!(folder) Pleroma.Backports.mkdir_p!(folder)
File.write!(previously_existing, "yey") File.write!(previously_existing, "yey")
assert File.exists?(previously_existing) assert File.exists?(previously_existing)

View file

@ -5,9 +5,8 @@
defmodule Pleroma.Instances.InstanceTest do defmodule Pleroma.Instances.InstanceTest do
alias Pleroma.Instances.Instance alias Pleroma.Instances.Instance
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.CommonAPI
use Oban.Testing, repo: Pleroma.Repo
use Pleroma.DataCase use Pleroma.DataCase
import ExUnit.CaptureLog import ExUnit.CaptureLog
@ -214,32 +213,14 @@ defmodule Pleroma.Instances.InstanceTest do
end end
end end
test "delete_users_and_activities/1 deletes remote instance users and activities" do test "delete_users_and_activities/1 schedules a job to delete the instance and users" do
[mario, luigi, _peach, wario] = insert(:user, nickname: "mario@mushroom.kingdom", name: "Mario")
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")
]
{:ok, post1} = CommonAPI.post(mario, %{status: "letsa go!"}) {:ok, _job} = Instance.delete_users_and_activities("mushroom.kingdom")
{: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") assert_enqueued(
:ok = ObanHelpers.perform(job) worker: Pleroma.Workers.DeleteWorker,
args: %{"op" => "delete_instance", "host" => "mushroom.kingdom"}
[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])
end end
end end

View file

@ -156,7 +156,7 @@ defmodule Pleroma.ObjectTest do
uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
File.mkdir_p!(uploads_dir) Pleroma.Backports.mkdir_p!(uploads_dir)
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "image/jpeg", content_type: "image/jpeg",

View file

@ -9,12 +9,12 @@ defmodule Pleroma.SafeZipTest do
setup do setup do
# Ensure tmp directory exists # Ensure tmp directory exists
File.mkdir_p!(@tmp_dir) Pleroma.Backports.mkdir_p!(@tmp_dir)
on_exit(fn -> on_exit(fn ->
# Clean up any files created during tests # Clean up any files created during tests
File.rm_rf!(@tmp_dir) File.rm_rf!(@tmp_dir)
File.mkdir_p!(@tmp_dir) Pleroma.Backports.mkdir_p!(@tmp_dir)
end) end)
:ok :ok
@ -89,7 +89,7 @@ defmodule Pleroma.SafeZipTest do
# For this test, we'll manually check if the file exists in the archive # For this test, we'll manually check if the file exists in the archive
# by extracting it and verifying it exists # by extracting it and verifying it exists
extract_dir = Path.join(@tmp_dir, "extract_check") 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) {:ok, files} = SafeZip.unzip_file(zip_path, extract_dir)
# Verify the root file was extracted # Verify the root file was extracted
@ -145,7 +145,7 @@ defmodule Pleroma.SafeZipTest do
test "can create zip with directories" do test "can create zip with directories" do
# Create a directory structure # Create a directory structure
dir_path = Path.join(@tmp_dir, "test_dir") 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_in_dir_path = Path.join(dir_path, "file_in_dir.txt")
File.write!(file_in_dir_path, "file in directory") 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 # Create a directory and a file in it
dir_path = Path.join(@tmp_dir, "file_in_dir") 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_in_dir_path = Path.join(dir_path, "test_file.txt")
File.write!(file_in_dir_path, "file in directory content") File.write!(file_in_dir_path, "file in directory content")

View file

@ -2792,6 +2792,15 @@ defmodule Pleroma.UserTest do
assert user_updated.also_known_as |> length() == 1 assert user_updated.also_known_as |> length() == 1
assert user2.ap_id in user_updated.also_known_as assert user2.ap_id in user_updated.also_known_as
end 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 end
describe "alias_users/1" do describe "alias_users/1" do

View file

@ -0,0 +1,139 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# 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

View file

@ -409,4 +409,105 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
assert decoded["cc"] == [] assert decoded["cc"] == []
end 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 end

View file

@ -143,4 +143,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do
assert {:ok, activity} = Transmogrifier.handle_incoming(data) assert {:ok, activity} = Transmogrifier.handle_incoming(data)
assert activity.data["type"] == "Like" assert activity.data["type"] == "Like"
end 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 end

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.AdminAPI.FrontendControllerTest do
setup do setup do
clear_config([:instance, :static_dir], @dir) clear_config([:instance, :static_dir], @dir)
File.mkdir_p!(Pleroma.Frontend.dir()) Pleroma.Backports.mkdir_p!(Pleroma.Frontend.dir())
on_exit(fn -> on_exit(fn ->
File.rm_rf(@dir) File.rm_rf(@dir)

View file

@ -8,8 +8,6 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
setup_all do setup_all do
@ -69,19 +67,19 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
test "DELETE /instances/:instance", %{conn: conn} do test "DELETE /instances/:instance", %{conn: conn} do
clear_config([:instance, :admin_privileges], [:instances_delete]) clear_config([:instance, :admin_privileges], [:instances_delete])
user = insert(:user, nickname: "lain@lain.com") insert(:user, nickname: "lain@lain.com")
post = insert(:note_activity, user: user)
response = response =
conn conn
|> delete("/api/pleroma/admin/instances/lain.com") |> delete("/api/pleroma/admin/instances/lain.com")
|> json_response(200) |> json_response(200)
[:ok] = ObanHelpers.perform_all()
assert response == "lain.com" 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], []) clear_config([:instance, :admin_privileges], [])

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceDocumentControllerTest do
@default_instance_panel ~s(<p>Welcome to <a href="https://pleroma.social" target="_blank">Pleroma!</a></p>) @default_instance_panel ~s(<p>Welcome to <a href="https://pleroma.social" target="_blank">Pleroma!</a></p>)
setup do setup do
File.mkdir_p!(@dir) Pleroma.Backports.mkdir_p!(@dir)
on_exit(fn -> File.rm_rf(@dir) end) on_exit(fn -> File.rm_rf(@dir) end)
end end

View file

@ -19,10 +19,33 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
"artist" => "lain", "artist" => "lain",
"album" => "lain radio", "album" => "lain radio",
"length" => "180000", "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
end end
@ -35,7 +58,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
title: "lain radio episode 1", title: "lain radio episode 1",
artist: "lain", artist: "lain",
album: "lain radio", 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} = {:ok, _activity} =
@ -43,7 +66,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
title: "lain radio episode 2", title: "lain radio episode 2",
artist: "lain", artist: "lain",
album: "lain radio", 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} = {:ok, _activity} =
@ -51,7 +74,7 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do
title: "lain radio episode 3", title: "lain radio episode 3",
artist: "lain", artist: "lain",
album: "lain radio", 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") conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles")

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
@dir "test/tmp/instance_static" @dir "test/tmp/instance_static"
setup do setup do
File.mkdir_p!(@dir) Pleroma.Backports.mkdir_p!(@dir)
on_exit(fn -> File.rm_rf(@dir) end) on_exit(fn -> File.rm_rf(@dir) end)
end end
@ -38,7 +38,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
clear_config([:frontends, :primary], %{"name" => name, "ref" => ref}) clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
path = "#{@dir}/frontends/#{name}/#{ref}" path = "#{@dir}/frontends/#{name}/#{ref}"
File.mkdir_p!(path) Pleroma.Backports.mkdir_p!(path)
File.write!("#{path}/index.html", "from frontend plug") File.write!("#{path}/index.html", "from frontend plug")
index = get(conn, "/") index = get(conn, "/")
@ -52,7 +52,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
clear_config([:frontends, :admin], %{"name" => name, "ref" => ref}) clear_config([:frontends, :admin], %{"name" => name, "ref" => ref})
path = "#{@dir}/frontends/#{name}/#{ref}" path = "#{@dir}/frontends/#{name}/#{ref}"
File.mkdir_p!(path) Pleroma.Backports.mkdir_p!(path)
File.write!("#{path}/index.html", "from frontend plug") File.write!("#{path}/index.html", "from frontend plug")
index = get(conn, "/pleroma/admin/") index = get(conn, "/pleroma/admin/")
@ -67,7 +67,7 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
clear_config([:frontends, :primary], %{"name" => name, "ref" => ref}) clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
path = "#{@dir}/frontends/#{name}/#{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") File.write!("#{path}/proxy/rr/ss/Ek7w8WPVcAApOvN.jpg:large", "FB image")
ConfigMock ConfigMock

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do
@dir "test/tmp/instance_static" @dir "test/tmp/instance_static"
setup do setup do
File.mkdir_p!(@dir) Pleroma.Backports.mkdir_p!(@dir)
on_exit(fn -> File.rm_rf(@dir) end) on_exit(fn -> File.rm_rf(@dir) end)
end end
@ -34,7 +34,7 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do
refute html_response(bundled_index, 200) == "from frontend plug" refute html_response(bundled_index, 200) == "from frontend plug"
path = "#{@dir}/frontends/#{name}/#{ref}" path = "#{@dir}/frontends/#{name}/#{ref}"
File.mkdir_p!(path) Pleroma.Backports.mkdir_p!(path)
File.write!("#{path}/index.html", "from frontend plug") File.write!("#{path}/index.html", "from frontend plug")
index = get(conn, "/") index = get(conn, "/")

View file

@ -0,0 +1,39 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# 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