Merge branch 'develop' into 'endorsements-api'
# Conflicts: # test/pleroma/web/pleroma_api/controllers/account_controller_test.exs
This commit is contained in:
commit
5ce3c12c28
641 changed files with 5697 additions and 1718 deletions
|
|
@ -26,7 +26,11 @@ defmodule Mix.Pleroma do
|
|||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
unless System.get_env("DEBUG") do
|
||||
Logger.remove_backend(:console)
|
||||
try do
|
||||
Logger.remove_backend(:console)
|
||||
catch
|
||||
:exit, _ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ defmodule Mix.Tasks.Pleroma.TestRunner do
|
|||
use Mix.Task
|
||||
|
||||
def run(args \\ []) do
|
||||
case System.cmd("mix", ["test"] ++ args, into: IO.stream(:stdio, :line)) do
|
||||
case System.cmd("mix", ["test", "--warnings-as-errors"] ++ args,
|
||||
into: IO.stream(:stdio, :line)
|
||||
) do
|
||||
{_, 0} ->
|
||||
:ok
|
||||
|
||||
|
|
|
|||
|
|
@ -43,9 +43,6 @@ defmodule Pleroma.Application do
|
|||
# every time the application is restarted, so we disable module
|
||||
# conflicts at runtime
|
||||
Code.compiler_options(ignore_module_conflict: true)
|
||||
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload
|
||||
# due to protocol consolidation warnings
|
||||
Code.compiler_options(warnings_as_errors: false)
|
||||
Pleroma.Telemetry.Logger.attach()
|
||||
Config.Holder.save_default()
|
||||
Pleroma.HTML.compile_scrubbers()
|
||||
|
|
@ -71,26 +68,11 @@ defmodule Pleroma.Application do
|
|||
Finch.start_link(name: MyFinch)
|
||||
end
|
||||
|
||||
if adapter == Tesla.Adapter.Gun do
|
||||
if version = Pleroma.OTPVersion.version() do
|
||||
[major, minor] =
|
||||
version
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_integer/1)
|
||||
|> Enum.take(2)
|
||||
|
||||
if (major == 22 and minor < 2) or major < 22 do
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
You are using gun adapter with OTP version #{version}, which doesn't support correct handling of unordered certificates chains. Please update your Erlang/OTP to at least 22.2.
|
||||
"
|
||||
end
|
||||
else
|
||||
raise "
|
||||
!!!OTP VERSION WARNING!!!
|
||||
To support correct handling of unordered certificates chains - OTP version must be > 22.2.
|
||||
"
|
||||
end
|
||||
# Disable warnings_as_errors at runtime, it breaks Phoenix live reload
|
||||
# due to protocol consolidation warnings
|
||||
# :warnings_as_errors is deprecated via Code.compiler_options/2 since 1.18
|
||||
if Version.compare(System.version(), "1.18.0") == :lt do
|
||||
Code.compiler_options(warnings_as_errors: false)
|
||||
end
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
|
|
|
|||
72
lib/pleroma/backports.ex
Normal file
72
lib/pleroma/backports.ex
Normal 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
|
||||
|
|
@ -302,7 +302,7 @@ defmodule Pleroma.ConfigDB do
|
|||
end
|
||||
|
||||
def to_elixir_types(%{"tuple" => entity}) do
|
||||
Enum.reduce(entity, {}, &Tuple.append(&2, to_elixir_types(&1)))
|
||||
Enum.reduce(entity, {}, &Tuple.insert_at(&2, tuple_size(&2), to_elixir_types(&1)))
|
||||
end
|
||||
|
||||
def to_elixir_types(entity) when is_map(entity) do
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -225,6 +225,97 @@ defmodule Pleroma.Emoji.Pack do
|
|||
end
|
||||
end
|
||||
|
||||
def download_zip(name, opts \\ %{}) do
|
||||
with :ok <- validate_not_empty([name]),
|
||||
:ok <- validate_new_pack(name),
|
||||
{:ok, archive_data} <- fetch_archive_data(opts),
|
||||
pack_path <- path_join_name_safe(emoji_path(), name),
|
||||
:ok <- create_pack_dir(pack_path),
|
||||
:ok <- safe_unzip(archive_data, pack_path) do
|
||||
ensure_pack_json(pack_path, archive_data, opts)
|
||||
else
|
||||
{:error, :empty_values} -> {:error, "Pack name cannot be empty"}
|
||||
{:error, reason} when is_binary(reason) -> {:error, reason}
|
||||
_ -> {:error, "Could not process pack"}
|
||||
end
|
||||
end
|
||||
|
||||
defp create_pack_dir(pack_path) do
|
||||
case File.mkdir_p(pack_path) do
|
||||
:ok -> :ok
|
||||
{:error, _} -> {:error, "Could not create the pack directory"}
|
||||
end
|
||||
end
|
||||
|
||||
defp safe_unzip(archive_data, pack_path) do
|
||||
case SafeZip.unzip_data(archive_data, pack_path) do
|
||||
{:ok, _} -> :ok
|
||||
{:error, reason} when is_binary(reason) -> {:error, reason}
|
||||
_ -> {:error, "Could not unzip pack"}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_new_pack(name) do
|
||||
pack_path = path_join_name_safe(emoji_path(), name)
|
||||
|
||||
if File.exists?(pack_path) do
|
||||
{:error, "Pack already exists, refusing to import #{name}"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_archive_data(%{url: url}) do
|
||||
case Pleroma.HTTP.get(url) do
|
||||
{:ok, %{status: 200, body: data}} -> {:ok, data}
|
||||
_ -> {:error, "Could not download pack"}
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_archive_data(%{file: %Plug.Upload{path: path}}) do
|
||||
case File.read(path) do
|
||||
{:ok, data} -> {:ok, data}
|
||||
_ -> {:error, "Could not read the uploaded pack file"}
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_archive_data(_) do
|
||||
{:error, "Neither file nor URL was present in the request"}
|
||||
end
|
||||
|
||||
defp ensure_pack_json(pack_path, archive_data, opts) do
|
||||
pack_json_path = Path.join(pack_path, "pack.json")
|
||||
|
||||
if not File.exists?(pack_json_path) do
|
||||
create_pack_json(pack_path, pack_json_path, archive_data, opts)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp create_pack_json(pack_path, pack_json_path, archive_data, opts) do
|
||||
emoji_map =
|
||||
Pleroma.Emoji.Loader.make_shortcode_to_file_map(
|
||||
pack_path,
|
||||
Map.get(opts, :exts, [".png", ".gif", ".jpg"])
|
||||
)
|
||||
|
||||
archive_sha = :crypto.hash(:sha256, archive_data) |> Base.encode16()
|
||||
|
||||
pack_json = %{
|
||||
pack: %{
|
||||
license: Map.get(opts, :license, ""),
|
||||
homepage: Map.get(opts, :homepage, ""),
|
||||
description: Map.get(opts, :description, ""),
|
||||
src: Map.get(opts, :url),
|
||||
src_sha256: archive_sha
|
||||
},
|
||||
files: emoji_map
|
||||
}
|
||||
|
||||
File.write!(pack_json_path, Jason.encode!(pack_json, pretty: true))
|
||||
end
|
||||
|
||||
@spec download(String.t(), String.t(), String.t()) :: {:ok, t()} | {:error, atom()}
|
||||
def download(name, url, as) do
|
||||
uri = url |> String.trim() |> URI.parse()
|
||||
|
|
@ -488,7 +579,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 +627,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 +652,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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -22,14 +22,18 @@ defmodule Pleroma.Gopher.Server do
|
|||
def init([ip, port]) do
|
||||
Logger.info("Starting gopher server on #{port}")
|
||||
|
||||
:ranch.start_listener(
|
||||
:gopher,
|
||||
100,
|
||||
:ranch_tcp,
|
||||
[ip: ip, port: port],
|
||||
__MODULE__.ProtocolHandler,
|
||||
[]
|
||||
)
|
||||
{:ok, _pid} =
|
||||
:ranch.start_listener(
|
||||
:gopher,
|
||||
:ranch_tcp,
|
||||
%{
|
||||
num_acceptors: 100,
|
||||
max_connections: 100,
|
||||
socket_opts: [ip: ip, port: port]
|
||||
},
|
||||
__MODULE__.ProtocolHandler,
|
||||
[]
|
||||
)
|
||||
|
||||
{:ok, %{ip: ip, port: port}}
|
||||
end
|
||||
|
|
@ -43,13 +47,13 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
def start_link(ref, socket, transport, opts) do
|
||||
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
|
||||
def start_link(ref, transport, opts) do
|
||||
pid = spawn_link(__MODULE__, :init, [ref, transport, opts])
|
||||
{:ok, pid}
|
||||
end
|
||||
|
||||
def init(ref, socket, transport, [] = _Opts) do
|
||||
:ok = :ranch.accept_ack(ref)
|
||||
def init(ref, transport, opts \\ []) do
|
||||
{:ok, socket} = :ranch.handshake(ref, opts)
|
||||
loop(socket, transport)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -130,4 +130,66 @@ defmodule Pleroma.Hashtag do
|
|||
end
|
||||
|
||||
def get_recipients_for_activity(_activity), do: []
|
||||
|
||||
def search(query, options \\ []) do
|
||||
limit = Keyword.get(options, :limit, 20)
|
||||
offset = Keyword.get(options, :offset, 0)
|
||||
|
||||
search_terms =
|
||||
query
|
||||
|> String.downcase()
|
||||
|> String.trim()
|
||||
|> String.split(~r/\s+/)
|
||||
|> Enum.filter(&(&1 != ""))
|
||||
|> Enum.map(&String.trim_leading(&1, "#"))
|
||||
|> Enum.filter(&(&1 != ""))
|
||||
|
||||
if Enum.empty?(search_terms) do
|
||||
[]
|
||||
else
|
||||
# Use PostgreSQL's ANY operator with array for efficient multi-term search
|
||||
# This is much more efficient than multiple OR clauses
|
||||
search_patterns = Enum.map(search_terms, &"%#{&1}%")
|
||||
|
||||
# Create ranking query that prioritizes exact matches and closer matches
|
||||
# Use a subquery to properly handle computed columns in ORDER BY
|
||||
base_query =
|
||||
from(ht in Hashtag,
|
||||
where: fragment("LOWER(?) LIKE ANY(?)", ht.name, ^search_patterns),
|
||||
select: %{
|
||||
name: ht.name,
|
||||
# Ranking: exact matches get highest priority (0)
|
||||
# then prefix matches (1), then contains (2)
|
||||
match_rank:
|
||||
fragment(
|
||||
"""
|
||||
CASE
|
||||
WHEN LOWER(?) = ANY(?) THEN 0
|
||||
WHEN LOWER(?) LIKE ANY(?) THEN 1
|
||||
ELSE 2
|
||||
END
|
||||
""",
|
||||
ht.name,
|
||||
^search_terms,
|
||||
ht.name,
|
||||
^Enum.map(search_terms, &"#{&1}%")
|
||||
),
|
||||
# Secondary sort by name length (shorter names first)
|
||||
name_length: fragment("LENGTH(?)", ht.name)
|
||||
}
|
||||
)
|
||||
|
||||
from(result in subquery(base_query),
|
||||
order_by: [
|
||||
asc: result.match_rank,
|
||||
asc: result.name_length,
|
||||
asc: result.name
|
||||
],
|
||||
limit: ^limit,
|
||||
offset: ^offset
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.map(& &1.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -105,20 +105,57 @@ defmodule Pleroma.HTTP do
|
|||
end
|
||||
|
||||
defp adapter_middlewares(Tesla.Adapter.Gun, extra_middleware) do
|
||||
[Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] ++
|
||||
default_middleware() ++
|
||||
[Pleroma.Tesla.Middleware.ConnectionPool] ++
|
||||
extra_middleware
|
||||
end
|
||||
|
||||
defp adapter_middlewares({Tesla.Adapter.Finch, _}, extra_middleware) do
|
||||
[Tesla.Middleware.FollowRedirects] ++ extra_middleware
|
||||
end
|
||||
|
||||
defp adapter_middlewares(_, extra_middleware) do
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
# Emulate redirects in test env, which are handled by adapters in other environments
|
||||
[Tesla.Middleware.FollowRedirects]
|
||||
else
|
||||
extra_middleware
|
||||
# A lot of tests are written expecting unencoded URLs
|
||||
# and the burden of fixing that is high. Also it makes
|
||||
# them hard to read. Tests will opt-in when we want to validate
|
||||
# the encoding is being done correctly.
|
||||
cond do
|
||||
Pleroma.Config.get(:env) == :test and Pleroma.Config.get(:test_url_encoding) ->
|
||||
default_middleware()
|
||||
|
||||
Pleroma.Config.get(:env) == :test ->
|
||||
# Emulate redirects in test env, which are handled by adapters in other environments
|
||||
[Tesla.Middleware.FollowRedirects]
|
||||
|
||||
# Hackney and Finch
|
||||
true ->
|
||||
default_middleware() ++ extra_middleware
|
||||
end
|
||||
end
|
||||
|
||||
defp default_middleware,
|
||||
do: [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.EncodeUrl]
|
||||
|
||||
def encode_url(url) when is_binary(url) do
|
||||
URI.parse(url)
|
||||
|> then(fn parsed ->
|
||||
path = encode_path(parsed.path)
|
||||
query = encode_query(parsed.query)
|
||||
|
||||
%{parsed | path: path, query: query}
|
||||
end)
|
||||
|> URI.to_string()
|
||||
end
|
||||
|
||||
defp encode_path(nil), do: nil
|
||||
|
||||
defp encode_path(path) when is_binary(path) do
|
||||
path
|
||||
|> URI.decode()
|
||||
|> URI.encode()
|
||||
end
|
||||
|
||||
defp encode_query(nil), do: nil
|
||||
|
||||
defp encode_query(query) when is_binary(query) do
|
||||
query
|
||||
|> URI.decode_query()
|
||||
|> URI.encode_query()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,25 +15,7 @@ defmodule Pleroma.Instances do
|
|||
|
||||
defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: Instance
|
||||
|
||||
defdelegate get_consistently_unreachable, to: Instance
|
||||
|
||||
def set_consistently_unreachable(url_or_host),
|
||||
do: set_unreachable(url_or_host, reachability_datetime_threshold())
|
||||
|
||||
def reachability_datetime_threshold do
|
||||
federation_reachability_timeout_days =
|
||||
Pleroma.Config.get([:instance, :federation_reachability_timeout_days], 0)
|
||||
|
||||
if federation_reachability_timeout_days > 0 do
|
||||
NaiveDateTime.add(
|
||||
NaiveDateTime.utc_now(),
|
||||
-federation_reachability_timeout_days * 24 * 3600,
|
||||
:second
|
||||
)
|
||||
else
|
||||
~N[0000-01-01 00:00:00]
|
||||
end
|
||||
end
|
||||
defdelegate get_unreachable, to: Instance
|
||||
|
||||
def host(url_or_host) when is_binary(url_or_host) do
|
||||
if url_or_host =~ ~r/^http/i do
|
||||
|
|
@ -42,4 +24,21 @@ defmodule Pleroma.Instances do
|
|||
url_or_host
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Schedules reachability checks for all unreachable instances"
|
||||
def check_all_unreachable do
|
||||
get_unreachable()
|
||||
|> Enum.each(fn {domain, _} ->
|
||||
Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain})
|
||||
|> Oban.insert()
|
||||
end)
|
||||
end
|
||||
|
||||
@doc "Deletes all users and activities for unreachable instances"
|
||||
def delete_all_unreachable do
|
||||
get_unreachable()
|
||||
|> Enum.each(fn {domain, _} ->
|
||||
Instance.delete(domain)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -51,7 +50,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
|> cast(params, [:software_name, :software_version, :software_repository])
|
||||
end
|
||||
|
||||
def filter_reachable([]), do: %{}
|
||||
def filter_reachable([]), do: []
|
||||
|
||||
def filter_reachable(urls_or_hosts) when is_list(urls_or_hosts) do
|
||||
hosts =
|
||||
|
|
@ -68,19 +67,15 @@ defmodule Pleroma.Instances.Instance do
|
|||
)
|
||||
|> Map.new(& &1)
|
||||
|
||||
reachability_datetime_threshold = Instances.reachability_datetime_threshold()
|
||||
|
||||
for entry <- Enum.filter(urls_or_hosts, &is_binary/1) do
|
||||
host = host(entry)
|
||||
unreachable_since = unreachable_since_by_host[host]
|
||||
|
||||
if !unreachable_since ||
|
||||
NaiveDateTime.compare(unreachable_since, reachability_datetime_threshold) == :gt do
|
||||
{entry, unreachable_since}
|
||||
if is_nil(unreachable_since) do
|
||||
entry
|
||||
end
|
||||
end
|
||||
|> Enum.filter(& &1)
|
||||
|> Map.new(& &1)
|
||||
end
|
||||
|
||||
def reachable?(url_or_host) when is_binary(url_or_host) do
|
||||
|
|
@ -88,7 +83,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
from(i in Instance,
|
||||
where:
|
||||
i.host == ^host(url_or_host) and
|
||||
i.unreachable_since <= ^Instances.reachability_datetime_threshold(),
|
||||
not is_nil(i.unreachable_since),
|
||||
select: true
|
||||
)
|
||||
)
|
||||
|
|
@ -97,9 +92,16 @@ defmodule Pleroma.Instances.Instance do
|
|||
def reachable?(url_or_host) when is_binary(url_or_host), do: true
|
||||
|
||||
def set_reachable(url_or_host) when is_binary(url_or_host) do
|
||||
%Instance{host: host(url_or_host)}
|
||||
|> changeset(%{unreachable_since: nil})
|
||||
|> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host)
|
||||
host = host(url_or_host)
|
||||
|
||||
result =
|
||||
%Instance{host: host}
|
||||
|> changeset(%{unreachable_since: nil})
|
||||
|> Repo.insert(on_conflict: {:replace, [:unreachable_since]}, conflict_target: :host)
|
||||
|
||||
Pleroma.Workers.ReachabilityWorker.delete_jobs_for_host(host)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def set_reachable(_), do: {:error, nil}
|
||||
|
|
@ -132,11 +134,9 @@ defmodule Pleroma.Instances.Instance do
|
|||
|
||||
def set_unreachable(_, _), do: {:error, nil}
|
||||
|
||||
def get_consistently_unreachable do
|
||||
reachability_datetime_threshold = Instances.reachability_datetime_threshold()
|
||||
|
||||
def get_unreachable do
|
||||
from(i in Instance,
|
||||
where: ^reachability_datetime_threshold > i.unreachable_since,
|
||||
where: not is_nil(i.unreachable_since),
|
||||
order_by: i.unreachable_since,
|
||||
select: {i.host, i.unreachable_since}
|
||||
)
|
||||
|
|
@ -296,20 +296,14 @@ defmodule Pleroma.Instances.Instance do
|
|||
Deletes all users from an instance in a background task, thus also deleting
|
||||
all of those users' activities and notifications.
|
||||
"""
|
||||
def delete_users_and_activities(host) when is_binary(host) do
|
||||
def delete(host) when is_binary(host) 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()
|
||||
@doc "Schedules reachability check for instance"
|
||||
def check_unreachable(domain) when is_binary(domain) do
|
||||
Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain})
|
||||
|> Oban.insert()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Language.LanguageDetector do
|
|||
def configured? do
|
||||
provider = get_provider()
|
||||
|
||||
!!provider and provider.configured?
|
||||
!!provider and provider.configured?()
|
||||
end
|
||||
|
||||
def missing_dependencies do
|
||||
|
|
@ -41,7 +41,7 @@ defmodule Pleroma.Language.LanguageDetector do
|
|||
text = prepare_text(text)
|
||||
word_count = text |> String.split(~r/\s+/) |> Enum.count()
|
||||
|
||||
if word_count < @words_threshold or !provider or !provider.configured? do
|
||||
if word_count < @words_threshold or !provider or !provider.configured?() do
|
||||
nil
|
||||
else
|
||||
with language <- provider.detect(text),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ defmodule Pleroma.Language.Translation do
|
|||
def configured? do
|
||||
provider = get_provider()
|
||||
|
||||
!!provider and provider.configured?
|
||||
!!provider and provider.configured?()
|
||||
end
|
||||
|
||||
def missing_dependencies do
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ defmodule Pleroma.Language.Translation.Provider do
|
|||
@callback supported_languages(type :: :string | :target) ::
|
||||
{:ok, [String.t()]} | {:error, atom()}
|
||||
|
||||
@callback languages_matrix() :: {:ok, Map.t()} | {:error, atom()}
|
||||
@callback languages_matrix() :: {:ok, map()} | {:error, atom()}
|
||||
|
||||
@callback name() :: String.t()
|
||||
|
||||
|
|
|
|||
|
|
@ -575,6 +575,12 @@ defmodule Pleroma.ModerationLog do
|
|||
"@#{actor_nickname} requested account backup for @#{user_nickname}"
|
||||
end
|
||||
|
||||
def get_log_entry_message(%ModerationLog{data: data}) do
|
||||
actor_name = get_in(data, ["actor", "nickname"]) || "unknown"
|
||||
action = data["action"] || "unknown"
|
||||
"@#{actor_name} performed action #{action}"
|
||||
end
|
||||
|
||||
defp nicknames_to_string(nicknames) do
|
||||
nicknames
|
||||
|> Enum.map(&"@#{&1}")
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ defmodule Pleroma.Notification do
|
|||
reblog
|
||||
poll
|
||||
status
|
||||
update
|
||||
}
|
||||
|
||||
def changeset(%Notification{} = notification, attrs) do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Object.Fetcher do
|
||||
alias Pleroma.HTTP
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
|
|
@ -19,8 +18,6 @@ defmodule Pleroma.Object.Fetcher do
|
|||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
@mix_env Mix.env()
|
||||
|
||||
@spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
|
||||
defp reinject_object(%Object{data: %{}} = object, new_data) do
|
||||
Logger.debug("Reinjecting object #{new_data["id"]}")
|
||||
|
|
@ -152,10 +149,6 @@ defmodule Pleroma.Object.Fetcher do
|
|||
{:ok, body} <- get_object(id),
|
||||
{:ok, data} <- safe_json_decode(body),
|
||||
:ok <- Containment.contain_origin_from_id(id, data) do
|
||||
if not Instances.reachable?(id) do
|
||||
Instances.set_reachable(id)
|
||||
end
|
||||
|
||||
{:ok, data}
|
||||
else
|
||||
{:scheme, _} ->
|
||||
|
|
@ -178,13 +171,8 @@ defmodule Pleroma.Object.Fetcher do
|
|||
def fetch_and_contain_remote_object_from_id(_id),
|
||||
do: {:error, "id must be a string"}
|
||||
|
||||
defp check_crossdomain_redirect(final_host, original_url)
|
||||
|
||||
# Handle the common case in tests where responses don't include URLs
|
||||
if @mix_env == :test do
|
||||
defp check_crossdomain_redirect(nil, _) do
|
||||
{:cross_domain_redirect, false}
|
||||
end
|
||||
defp check_crossdomain_redirect(final_host, _original_url) when is_nil(final_host) do
|
||||
{:cross_domain_redirect, false}
|
||||
end
|
||||
|
||||
defp check_crossdomain_redirect(final_host, original_url) do
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Object.Updater do
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
||||
|
|
@ -115,6 +116,7 @@ defmodule Pleroma.Object.Updater do
|
|||
# Choices are the same, but counts are different
|
||||
to_be_updated
|
||||
|> Map.put(key, updated_object[key])
|
||||
|> Maps.put_if_present("votersCount", updated_object["votersCount"])
|
||||
else
|
||||
# Choices (or vote type) have changed, do not allow this
|
||||
_ -> to_be_updated
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.OTPVersion do
|
||||
@spec version() :: String.t() | nil
|
||||
def version do
|
||||
# OTP Version https://erlang.org/doc/system_principles/versions.html#otp-version
|
||||
[
|
||||
Path.join(:code.root_dir(), "OTP_VERSION"),
|
||||
Path.join([:code.root_dir(), "releases", :erlang.system_info(:otp_release), "OTP_VERSION"])
|
||||
]
|
||||
|> get_version_from_files()
|
||||
end
|
||||
|
||||
@spec get_version_from_files([Path.t()]) :: String.t() | nil
|
||||
def get_version_from_files([]), do: nil
|
||||
|
||||
def get_version_from_files([path | paths]) do
|
||||
if File.exists?(path) do
|
||||
path
|
||||
|> File.read!()
|
||||
|> String.replace(~r/\r|\n|\s/, "")
|
||||
else
|
||||
get_version_from_files(paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -158,6 +158,8 @@ defmodule Pleroma.ReverseProxy do
|
|||
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
|
||||
method = method |> String.downcase() |> String.to_existing_atom()
|
||||
|
||||
url = maybe_encode_url(url)
|
||||
|
||||
case client().request(method, url, headers, "", opts) do
|
||||
{:ok, code, headers, client} when code in @valid_resp_codes ->
|
||||
{:ok, code, downcase_headers(headers), client}
|
||||
|
|
@ -449,4 +451,18 @@ defmodule Pleroma.ReverseProxy do
|
|||
_ -> delete_resp_header(conn, "content-length")
|
||||
end
|
||||
end
|
||||
|
||||
# Only when Tesla adapter is Hackney or Finch does the URL
|
||||
# need encoding before Reverse Proxying as both end up
|
||||
# using the raw Hackney client and cannot leverage our
|
||||
# EncodeUrl Tesla middleware
|
||||
# Also do it for test environment
|
||||
defp maybe_encode_url(url) do
|
||||
case Application.get_env(:tesla, :adapter) do
|
||||
Tesla.Adapter.Hackney -> Pleroma.HTTP.encode_url(url)
|
||||
{Tesla.Adapter.Finch, _} -> Pleroma.HTTP.encode_url(url)
|
||||
Tesla.Mock -> Pleroma.HTTP.encode_url(url)
|
||||
_ -> url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,10 +56,6 @@ defmodule Pleroma.SafeZip do
|
|||
{_, true} <- {:safe_path, safe_path?(path)} do
|
||||
{:cont, {:ok, maybe_add_file(type, path, fl)}}
|
||||
else
|
||||
{:get_type, e} ->
|
||||
{:halt,
|
||||
{:error, "Couldn't determine file type of ZIP entry at #{path} (#{inspect(e)})"}}
|
||||
|
||||
{:type, _} ->
|
||||
{:halt, {:error, "Potentially unsafe file type in ZIP at: #{path}"}}
|
||||
|
||||
|
|
|
|||
|
|
@ -157,26 +157,55 @@ defmodule Pleroma.Search.QdrantSearch do
|
|||
end
|
||||
|
||||
defmodule Pleroma.Search.QdrantSearch.OpenAIClient do
|
||||
use Tesla
|
||||
alias Pleroma.Config.Getting, as: Config
|
||||
|
||||
plug(Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :openai_url]))
|
||||
plug(Tesla.Middleware.JSON)
|
||||
def post(path, body) do
|
||||
Tesla.post(client(), path, body)
|
||||
end
|
||||
|
||||
plug(Tesla.Middleware.Headers, [
|
||||
{"Authorization",
|
||||
"Bearer #{Pleroma.Config.get([Pleroma.Search.QdrantSearch, :openai_api_key])}"}
|
||||
])
|
||||
defp client do
|
||||
Tesla.client(middleware())
|
||||
end
|
||||
|
||||
defp middleware do
|
||||
[
|
||||
{Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :openai_url])},
|
||||
Tesla.Middleware.JSON,
|
||||
{Tesla.Middleware.Headers,
|
||||
[
|
||||
{"Authorization", "Bearer #{Config.get([Pleroma.Search.QdrantSearch, :openai_api_key])}"}
|
||||
]}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.Search.QdrantSearch.QdrantClient do
|
||||
use Tesla
|
||||
alias Pleroma.Config.Getting, as: Config
|
||||
|
||||
plug(Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :qdrant_url]))
|
||||
plug(Tesla.Middleware.JSON)
|
||||
def delete(path) do
|
||||
Tesla.delete(client(), path)
|
||||
end
|
||||
|
||||
plug(Tesla.Middleware.Headers, [
|
||||
{"api-key", Pleroma.Config.get([Pleroma.Search.QdrantSearch, :qdrant_api_key])}
|
||||
])
|
||||
def post(path, body) do
|
||||
Tesla.post(client(), path, body)
|
||||
end
|
||||
|
||||
def put(path, body) do
|
||||
Tesla.put(client(), path, body)
|
||||
end
|
||||
|
||||
defp client do
|
||||
Tesla.client(middleware())
|
||||
end
|
||||
|
||||
defp middleware do
|
||||
[
|
||||
{Tesla.Middleware.BaseUrl, Config.get([Pleroma.Search.QdrantSearch, :qdrant_url])},
|
||||
Tesla.Middleware.JSON,
|
||||
{Tesla.Middleware.Headers,
|
||||
[
|
||||
{"api-key", Pleroma.Config.get([Pleroma.Search.QdrantSearch, :qdrant_api_key])}
|
||||
]}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Signature do
|
||||
@behaviour Pleroma.Signature.API
|
||||
@behaviour HTTPSignatures.Adapter
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
|
|
|||
14
lib/pleroma/signature/api.ex
Normal file
14
lib/pleroma/signature/api.ex
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Signature.API do
|
||||
@moduledoc """
|
||||
Behaviour for signing requests and producing HTTP Date headers.
|
||||
|
||||
This is used to allow tests to replace the signing implementation with Mox.
|
||||
"""
|
||||
|
||||
@callback sign(user :: Pleroma.User.t(), headers :: map()) :: String.t()
|
||||
@callback signed_date() :: String.t()
|
||||
end
|
||||
29
lib/pleroma/tesla/middleware/encode_url.ex
Normal file
29
lib/pleroma/tesla/middleware/encode_url.ex
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2025 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Tesla.Middleware.EncodeUrl do
|
||||
@moduledoc """
|
||||
Middleware to encode URLs properly
|
||||
|
||||
We must decode and then re-encode to ensure correct encoding.
|
||||
If we only encode it will re-encode each % as %25 causing a space
|
||||
already encoded as %20 to be %2520.
|
||||
|
||||
Similar problem for query parameters which need spaces to be the + character
|
||||
"""
|
||||
|
||||
@behaviour Tesla.Middleware
|
||||
|
||||
@impl Tesla.Middleware
|
||||
def call(%Tesla.Env{url: url} = env, next, _) do
|
||||
url = Pleroma.HTTP.encode_url(url)
|
||||
|
||||
env = %{env | url: url}
|
||||
|
||||
case Tesla.run(env, next) do
|
||||
{:ok, env} -> {:ok, env}
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -308,7 +308,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def binary_id(%User{} = user), do: binary_id(user.id)
|
||||
|
||||
@doc "Returns status account"
|
||||
@doc "Returns account status"
|
||||
@spec account_status(User.t()) :: account_status()
|
||||
def account_status(%User{is_active: false}), do: :deactivated
|
||||
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
|
||||
|
|
@ -801,13 +801,6 @@ defmodule Pleroma.User do
|
|||
when is_nil(password) do
|
||||
params = Map.put_new(params, :accepts_chat_messages, true)
|
||||
|
||||
params =
|
||||
if Map.has_key?(params, :email) do
|
||||
Map.put_new(params, :email, params[:email])
|
||||
else
|
||||
params
|
||||
end
|
||||
|
||||
struct
|
||||
|> cast(params, [
|
||||
:name,
|
||||
|
|
@ -895,7 +888,7 @@ defmodule Pleroma.User do
|
|||
end)
|
||||
end
|
||||
|
||||
def validate_email_not_in_blacklisted_domain(changeset, field) do
|
||||
defp validate_email_not_in_blacklisted_domain(changeset, field) do
|
||||
validate_change(changeset, field, fn _, value ->
|
||||
valid? =
|
||||
Config.get([User, :email_blacklist])
|
||||
|
|
@ -912,9 +905,9 @@ defmodule Pleroma.User do
|
|||
end)
|
||||
end
|
||||
|
||||
def maybe_validate_required_email(changeset, true), do: changeset
|
||||
defp maybe_validate_required_email(changeset, true), do: changeset
|
||||
|
||||
def maybe_validate_required_email(changeset, _) do
|
||||
defp maybe_validate_required_email(changeset, _) do
|
||||
if Config.get([:instance, :account_activation_required]) do
|
||||
validate_required(changeset, [:email])
|
||||
else
|
||||
|
|
@ -1109,15 +1102,15 @@ defmodule Pleroma.User do
|
|||
|
||||
defp maybe_send_registration_email(_), do: {:ok, :noop}
|
||||
|
||||
def needs_update?(%User{local: true}), do: false
|
||||
defp needs_update?(%User{local: true}), do: false
|
||||
|
||||
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
||||
defp needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
||||
|
||||
def needs_update?(%User{local: false} = user) do
|
||||
defp needs_update?(%User{local: false} = user) do
|
||||
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
|
||||
end
|
||||
|
||||
def needs_update?(_), do: true
|
||||
defp needs_update?(_), do: true
|
||||
|
||||
@spec maybe_direct_follow(User.t(), User.t()) ::
|
||||
{:ok, User.t(), User.t()} | {:error, String.t()}
|
||||
|
|
@ -1708,7 +1701,9 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def block(%User{} = blocker, %User{} = blocked) do
|
||||
def block(blocker, blocked, params \\ %{})
|
||||
|
||||
def block(%User{} = blocker, %User{} = blocked, params) do
|
||||
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
||||
blocker =
|
||||
if following?(blocker, blocked) do
|
||||
|
|
@ -1738,12 +1733,33 @@ defmodule Pleroma.User do
|
|||
|
||||
{:ok, blocker} = update_follower_count(blocker)
|
||||
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||
add_to_block(blocker, blocked)
|
||||
|
||||
duration = Map.get(params, :duration, 0)
|
||||
|
||||
expires_at =
|
||||
if duration > 0 do
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(duration)
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
user_block = add_to_block(blocker, blocked, expires_at)
|
||||
|
||||
if duration > 0 do
|
||||
Pleroma.Workers.MuteExpireWorker.new(
|
||||
%{"op" => "unblock_user", "blocker_id" => blocker.id, "blocked_id" => blocked.id},
|
||||
scheduled_at: expires_at
|
||||
)
|
||||
|> Oban.insert()
|
||||
end
|
||||
|
||||
user_block
|
||||
end
|
||||
|
||||
# helper to handle the block given only an actor's AP id
|
||||
def block(%User{} = blocker, %{ap_id: ap_id}) do
|
||||
block(blocker, get_cached_by_ap_id(ap_id))
|
||||
def block(%User{} = blocker, %{ap_id: ap_id}, params) do
|
||||
block(blocker, get_cached_by_ap_id(ap_id), params)
|
||||
end
|
||||
|
||||
def unblock(%User{} = blocker, %User{} = blocked) do
|
||||
|
|
@ -1984,7 +2000,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
@spec purge_user_changeset(User.t()) :: Ecto.Changeset.t()
|
||||
def purge_user_changeset(user) do
|
||||
defp purge_user_changeset(user) do
|
||||
# "Right to be forgotten"
|
||||
# https://gdpr.eu/right-to-be-forgotten/
|
||||
change(user, %{
|
||||
|
|
@ -2156,7 +2172,7 @@ defmodule Pleroma.User do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
|
||||
defp delete_notifications_from_user_activities(%User{ap_id: ap_id}) do
|
||||
Notification
|
||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||
|> where([n, a], fragment("? = ?", a.actor, ^ap_id))
|
||||
|
|
@ -2615,7 +2631,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
# Internal function; public one is `deactivate/2`
|
||||
# Internal function; public one is `set_activation/2`
|
||||
defp set_activation_status(user, status) do
|
||||
user
|
||||
|> cast(%{is_active: status}, [:is_active])
|
||||
|
|
@ -2634,7 +2650,7 @@ defmodule Pleroma.User do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def validate_fields(changeset, remote? \\ false) do
|
||||
defp validate_fields(changeset, remote?) do
|
||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||
limit = Config.get([:instance, limit_name], 0)
|
||||
|
||||
|
|
@ -2779,10 +2795,10 @@ defmodule Pleroma.User do
|
|||
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
|
||||
end
|
||||
|
||||
@spec add_to_block(User.t(), User.t()) ::
|
||||
@spec add_to_block(User.t(), User.t(), integer() | nil) ::
|
||||
{:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp add_to_block(%User{} = user, %User{} = blocked) do
|
||||
with {:ok, relationship} <- UserRelationship.create_block(user, blocked) do
|
||||
defp add_to_block(%User{} = user, %User{} = blocked, expires_at) do
|
||||
with {:ok, relationship} <- UserRelationship.create_block(user, blocked, expires_at) do
|
||||
@cachex.del(:user_cache, "blocked_users_ap_ids:#{user.ap_id}")
|
||||
{:ok, relationship}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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)},
|
||||
|
|
|
|||
|
|
@ -193,7 +193,8 @@ defmodule Pleroma.UserRelationship do
|
|||
{[:mute], []}
|
||||
|
||||
nil ->
|
||||
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
|
||||
{[:block, :mute, :notification_mute, :reblog_mute, :endorsement],
|
||||
[:block, :inverse_subscription]}
|
||||
|
||||
unknown ->
|
||||
raise "Unsupported :subset option value: #{inspect(unknown)}"
|
||||
|
|
|
|||
|
|
@ -414,10 +414,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
with flag_data <- make_flag_data(params, additional),
|
||||
{:ok, activity} <- insert(flag_data, local),
|
||||
{:ok, stripped_activity} <- strip_report_status_data(activity),
|
||||
_ <- notify_and_stream(activity),
|
||||
:ok <-
|
||||
maybe_federate(stripped_activity) do
|
||||
:ok <- maybe_federate(activity) do
|
||||
User.all_users_with_privilege(:reports_manage_reports)
|
||||
|> Enum.filter(fn user -> user.ap_id != actor end)
|
||||
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
||||
|
|
@ -1065,6 +1063,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
|
||||
end
|
||||
|
||||
defp restrict_reblogs(query, %{only_reblogs: true}) do
|
||||
from(activity in query, where: fragment("?->>'type' = 'Announce'", activity.data))
|
||||
end
|
||||
|
||||
defp restrict_reblogs(query, _), do: query
|
||||
|
||||
defp restrict_muted(query, %{with_muted: true}), do: query
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
)
|
||||
|
||||
plug(:log_inbox_metadata when action in [:inbox])
|
||||
plug(:set_requester_reachable when action in [:inbox])
|
||||
plug(:relay_active? when action in [:relay])
|
||||
|
||||
defp relay_active?(conn, _) do
|
||||
|
|
@ -274,13 +273,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
|
||||
with %User{is_active: true} = recipient <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, %User{is_active: true} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
|
||||
with {:recipient_exists, %User{} = recipient} <-
|
||||
{:recipient_exists, User.get_cached_by_nickname(nickname)},
|
||||
{:sender_exists, {:ok, %User{} = actor}} <-
|
||||
{:sender_exists, User.get_or_fetch_by_ap_id(params["actor"])},
|
||||
{:recipient_active, true} <- {:recipient_active, recipient.is_active},
|
||||
{:sender_active, true} <- {:sender_active, actor.is_active},
|
||||
true <- Utils.recipient_in_message(recipient, actor, params),
|
||||
params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
|
||||
Federator.incoming_ap_doc(params)
|
||||
json(conn, "ok")
|
||||
else
|
||||
{:recipient_exists, _} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json("User does not exist")
|
||||
|
||||
{:sender_exists, _} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json("Sender does not exist")
|
||||
|
||||
{:recipient_active, _} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json("User deactivated")
|
||||
|
||||
{:sender_active, _} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json("Sender deactivated")
|
||||
|
||||
_ ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|
|
@ -520,15 +543,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|> json(dgettext("errors", "error"))
|
||||
end
|
||||
|
||||
defp set_requester_reachable(%Plug.Conn{} = conn, _) do
|
||||
with actor <- conn.params["actor"],
|
||||
true <- is_binary(actor) do
|
||||
Pleroma.Instances.set_reachable(actor)
|
||||
end
|
||||
|
||||
conn
|
||||
end
|
||||
|
||||
defp log_inbox_metadata(%{params: %{"actor" => actor, "type" => type}} = conn, _) do
|
||||
Logger.metadata(actor: actor, type: type)
|
||||
conn
|
||||
|
|
|
|||
|
|
@ -327,8 +327,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
}, []}
|
||||
end
|
||||
|
||||
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||
def block(blocker, blocked) do
|
||||
@spec block(User.t(), User.t(), map()) :: {:ok, map(), keyword()}
|
||||
def block(blocker, blocked, params \\ %{}) do
|
||||
{:ok,
|
||||
%{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
|
|
@ -336,7 +336,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
|||
"actor" => blocker.ap_id,
|
||||
"object" => blocked.ap_id,
|
||||
"to" => [blocked.ap_id]
|
||||
}, []}
|
||||
}, Keyword.new(params)}
|
||||
end
|
||||
|
||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||
|
|
|
|||
61
lib/pleroma/web/activity_pub/mrf/quiet_reply.ex
Normal file
61
lib/pleroma/web/activity_pub/mrf/quiet_reply.ex
Normal 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
|
||||
|
|
@ -15,7 +15,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy do
|
|||
else
|
||||
{:local, true} -> {:ok, object}
|
||||
{:reject, message} -> {:reject, message}
|
||||
error -> {:reject, error}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -200,14 +200,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
end
|
||||
|
||||
def validate(%{"type" => type} = object, meta)
|
||||
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
|
||||
when type in ~w[Accept Reject Follow Like EmojiReact Announce
|
||||
ChatMessage Answer] do
|
||||
validator =
|
||||
case type do
|
||||
"Accept" -> AcceptRejectValidator
|
||||
"Reject" -> AcceptRejectValidator
|
||||
"Follow" -> FollowValidator
|
||||
"Update" -> UpdateValidator
|
||||
"Like" -> LikeValidator
|
||||
"EmojiReact" -> EmojiReactValidator
|
||||
"Announce" -> AnnounceValidator
|
||||
|
|
@ -215,16 +214,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
"Answer" -> AnswerValidator
|
||||
end
|
||||
|
||||
cast_func =
|
||||
if type == "Update" do
|
||||
fn o -> validator.cast_and_validate(o, meta) end
|
||||
else
|
||||
fn o -> validator.cast_and_validate(o) end
|
||||
end
|
||||
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> cast_func.()
|
||||
|> validator.cast_and_validate()
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(%{"type" => type} = object, meta) when type == "Update" do
|
||||
with {:ok, object} <-
|
||||
object
|
||||
|> UpdateValidator.cast_and_validate(meta)
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
{:ok, object, meta}
|
||||
|
|
|
|||
|
|
@ -50,13 +50,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
|||
end
|
||||
|
||||
def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
|
||||
name =
|
||||
cond do
|
||||
"#" <> name -> name
|
||||
name -> name
|
||||
end
|
||||
|> String.downcase()
|
||||
|
||||
name = String.downcase(name)
|
||||
data = Map.put(data, "name", name)
|
||||
|
||||
struct
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Publisher.Prepared
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Workers.PublisherWorker
|
||||
|
||||
require Pleroma.Constants
|
||||
|
|
@ -26,6 +25,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
ActivityPub outgoing federation module.
|
||||
"""
|
||||
|
||||
@signature_impl Application.compile_env(
|
||||
:pleroma,
|
||||
[__MODULE__, :signature_impl],
|
||||
Pleroma.Signature
|
||||
)
|
||||
|
||||
@transmogrifier_impl Application.compile_env(
|
||||
:pleroma,
|
||||
[__MODULE__, :transmogrifier_impl],
|
||||
Pleroma.Web.ActivityPub.Transmogrifier
|
||||
)
|
||||
|
||||
@doc """
|
||||
Enqueue publishing a single activity.
|
||||
"""
|
||||
|
|
@ -68,7 +79,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
Determine if an activity can be represented by running it through Transmogrifier.
|
||||
"""
|
||||
def representable?(%Activity{} = activity) do
|
||||
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
|
||||
with {:ok, _data} <- @transmogrifier_impl.prepare_outgoing(activity.data) do
|
||||
true
|
||||
else
|
||||
_e ->
|
||||
|
|
@ -91,9 +102,32 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
Logger.debug("Federating #{ap_id} to #{inbox}")
|
||||
uri = %{path: path} = URI.parse(inbox)
|
||||
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
{:ok, data} = @transmogrifier_impl.prepare_outgoing(activity.data)
|
||||
|
||||
cc = Map.get(params, :cc, [])
|
||||
{actor, data} =
|
||||
with {_, false} <- {:actor_changed?, data["actor"] != activity.data["actor"]} do
|
||||
{actor, data}
|
||||
else
|
||||
{:actor_changed?, true} ->
|
||||
# If prepare_outgoing changes the actor, re-get it from the db
|
||||
new_actor = User.get_cached_by_ap_id(data["actor"])
|
||||
{new_actor, data}
|
||||
end
|
||||
|
||||
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
|
||||
|
|
@ -102,10 +136,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
|
||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||
|
||||
date = Pleroma.Signature.signed_date()
|
||||
date = @signature_impl.signed_date()
|
||||
|
||||
signature =
|
||||
Pleroma.Signature.sign(actor, %{
|
||||
@signature_impl.sign(actor, %{
|
||||
"(request-target)": "post #{path}",
|
||||
host: signature_host(uri),
|
||||
"content-length": byte_size(json),
|
||||
|
|
@ -148,17 +182,9 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
{"digest", p.digest}
|
||||
]
|
||||
) do
|
||||
if not is_nil(p.unreachable_since) do
|
||||
Instances.set_reachable(p.inbox)
|
||||
end
|
||||
|
||||
result
|
||||
else
|
||||
{_post_result, %{status: code} = response} = e ->
|
||||
if is_nil(p.unreachable_since) do
|
||||
Instances.set_unreachable(p.inbox)
|
||||
end
|
||||
|
||||
Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code)
|
||||
Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}")
|
||||
|
||||
|
|
@ -179,10 +205,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
connection_pool_snooze()
|
||||
|
||||
e ->
|
||||
if is_nil(p.unreachable_since) do
|
||||
Instances.set_unreachable(p.inbox)
|
||||
end
|
||||
|
||||
Logger.metadata(activity: p.activity_id, inbox: p.inbox)
|
||||
Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}")
|
||||
{:error, e}
|
||||
|
|
@ -294,7 +316,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
|
||||
[priority_recipients, recipients] = recipients(actor, activity)
|
||||
|
||||
inboxes =
|
||||
[priority_inboxes, other_inboxes] =
|
||||
[priority_recipients, recipients]
|
||||
|> Enum.map(fn recipients ->
|
||||
recipients
|
||||
|
|
@ -307,8 +329,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
end)
|
||||
|
||||
Repo.checkout(fn ->
|
||||
Enum.each(inboxes, fn inboxes ->
|
||||
Enum.each(inboxes, fn {inbox, unreachable_since} ->
|
||||
Enum.each([priority_inboxes, other_inboxes], fn inboxes ->
|
||||
Enum.each(inboxes, fn inbox ->
|
||||
%User{ap_id: ap_id} = Enum.find(recipients, fn actor -> actor.inbox == inbox end)
|
||||
|
||||
# Get all the recipients on the same host and add them to cc. Otherwise, a remote
|
||||
|
|
@ -318,8 +340,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
__MODULE__.enqueue_one(%{
|
||||
inbox: inbox,
|
||||
cc: cc,
|
||||
activity_id: activity.id,
|
||||
unreachable_since: unreachable_since
|
||||
activity_id: activity.id
|
||||
})
|
||||
end)
|
||||
end)
|
||||
|
|
@ -352,12 +373,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
|> Enum.each(fn {inboxes, priority} ->
|
||||
inboxes
|
||||
|> Instances.filter_reachable()
|
||||
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||
|> Enum.each(fn inbox ->
|
||||
__MODULE__.enqueue_one(
|
||||
%{
|
||||
inbox: inbox,
|
||||
activity_id: activity.id,
|
||||
unreachable_since: unreachable_since
|
||||
activity_id: activity.id
|
||||
},
|
||||
priority: priority
|
||||
)
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
|||
) do
|
||||
with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
|
||||
%User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
|
||||
User.block(blocker, blocked)
|
||||
User.block(blocker, blocked, Enum.into(meta, %{}))
|
||||
end
|
||||
|
||||
{:ok, object, meta}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
@moduledoc """
|
||||
A module to handle coding from internal to wire ActivityPub and back.
|
||||
"""
|
||||
@behaviour Pleroma.Web.ActivityPub.Transmogrifier.API
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Maps
|
||||
|
|
@ -664,6 +665,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
|
||||
|
|
@ -891,6 +910,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def prepare_outgoing(%{"type" => "Flag"} = data) do
|
||||
with {:ok, stripped_activity} <- Utils.strip_report_status_data(data),
|
||||
stripped_activity <- Utils.maybe_anonymize_reporter(stripped_activity),
|
||||
stripped_activity <- Map.merge(stripped_activity, Utils.make_json_ld_header()) do
|
||||
{:ok, stripped_activity}
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_outgoing(%{"type" => _type} = data) do
|
||||
data =
|
||||
data
|
||||
|
|
|
|||
11
lib/pleroma/web/activity_pub/transmogrifier/api.ex
Normal file
11
lib/pleroma/web/activity_pub/transmogrifier/api.ex
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# 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.Transmogrifier.API do
|
||||
@moduledoc """
|
||||
Behaviour for the subset of Transmogrifier used by Publisher.
|
||||
"""
|
||||
|
||||
@callback prepare_outgoing(map()) :: {:ok, map()} | {:error, term()}
|
||||
end
|
||||
|
|
@ -82,7 +82,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
def unaddressed_message?(params),
|
||||
do:
|
||||
[params["to"], params["cc"], params["bto"], params["bcc"]]
|
||||
|> Enum.all?(&is_nil(&1))
|
||||
|> Enum.all?(fn
|
||||
nil -> true
|
||||
[] -> true
|
||||
_ -> false
|
||||
end)
|
||||
|
||||
@spec recipient_in_message(User.t(), User.t(), map()) :: boolean()
|
||||
def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params),
|
||||
|
|
@ -859,8 +863,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|
||||
def update_report_state(_, _), do: {:error, "Unsupported state"}
|
||||
|
||||
def strip_report_status_data(activity) do
|
||||
[actor | reported_activities] = activity.data["object"]
|
||||
def strip_report_status_data(%Activity{} = activity) do
|
||||
with {:ok, new_data} <- strip_report_status_data(activity.data) do
|
||||
{:ok, %{activity | data: new_data}}
|
||||
end
|
||||
end
|
||||
|
||||
def strip_report_status_data(data) do
|
||||
[actor | reported_activities] = data["object"]
|
||||
|
||||
stripped_activities =
|
||||
Enum.reduce(reported_activities, [], fn act, acc ->
|
||||
|
|
@ -870,9 +880,36 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
end
|
||||
end)
|
||||
|
||||
new_data = put_in(activity.data, ["object"], [actor | stripped_activities])
|
||||
new_data = put_in(data, ["object"], [actor | stripped_activities])
|
||||
|
||||
{:ok, %{activity | data: new_data}}
|
||||
{:ok, new_data}
|
||||
end
|
||||
|
||||
def get_anonymized_reporter do
|
||||
with true <- Pleroma.Config.get([:activitypub, :anonymize_reporter]),
|
||||
nickname when is_binary(nickname) <-
|
||||
Pleroma.Config.get([:activitypub, :anonymize_reporter_local_nickname]),
|
||||
%User{ap_id: ap_id, local: true} <- User.get_cached_by_nickname(nickname) do
|
||||
ap_id
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_anonymize_reporter(%Activity{data: data} = activity) do
|
||||
new_data = maybe_anonymize_reporter(data)
|
||||
%Activity{activity | actor: new_data["actor"], data: new_data}
|
||||
end
|
||||
|
||||
def maybe_anonymize_reporter(activity) do
|
||||
ap_id = get_anonymized_reporter()
|
||||
|
||||
if is_binary(ap_id) do
|
||||
activity
|
||||
|> Map.put("actor", ap_id)
|
||||
else
|
||||
activity
|
||||
end
|
||||
end
|
||||
|
||||
def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
|
||||
|
|
|
|||
|
|
@ -240,6 +240,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
render_error(conn, :not_found, "No such permission_group")
|
||||
end
|
||||
|
||||
def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
|
||||
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||
end
|
||||
|
||||
def right_delete(
|
||||
%{assigns: %{user: admin}} = conn,
|
||||
%{
|
||||
|
|
@ -265,10 +269,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
json(conn, fields)
|
||||
end
|
||||
|
||||
def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
|
||||
render_error(conn, :forbidden, "You can't revoke your own admin status.")
|
||||
end
|
||||
|
||||
@doc "Get a password reset token (base64 string) for given nickname"
|
||||
def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
|
||||
|
|
@ -335,13 +335,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
if params["password"] do
|
||||
User.force_password_reset_async(user)
|
||||
end
|
||||
|
||||
ModerationLog.insert_log(%{
|
||||
actor: admin,
|
||||
subject: [user],
|
||||
action: "force_password_reset"
|
||||
})
|
||||
ModerationLog.insert_log(%{
|
||||
actor: admin,
|
||||
subject: [user],
|
||||
action: "force_password_reset"
|
||||
})
|
||||
end
|
||||
|
||||
json(conn, %{status: "success"})
|
||||
else
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceController do
|
|||
end
|
||||
|
||||
def delete(conn, %{"instance" => instance}) do
|
||||
with {:ok, _job} <- Instance.delete_users_and_activities(instance) do
|
||||
with {:ok, _job} <- Instance.delete(instance) do
|
||||
json(conn, instance)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -158,6 +158,6 @@ defmodule Pleroma.Web.ApiSpec do
|
|||
}
|
||||
}
|
||||
# discover request/response schemas from path specs
|
||||
|> OpenApiSpex.resolve_schema_modules()
|
||||
|> then(&OpenApiSpex.resolve_schema_modules/1)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -143,6 +143,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
"Include statuses from muted accounts."
|
||||
),
|
||||
Operation.parameter(:exclude_reblogs, :query, BooleanLike.schema(), "Exclude reblogs"),
|
||||
Operation.parameter(
|
||||
:only_reblogs,
|
||||
:query,
|
||||
BooleanLike.schema(),
|
||||
"Include only reblogs"
|
||||
),
|
||||
Operation.parameter(:exclude_replies, :query, BooleanLike.schema(), "Exclude replies"),
|
||||
Operation.parameter(
|
||||
:exclude_visibilities,
|
||||
|
|
@ -284,18 +290,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
:query,
|
||||
%Schema{allOf: [BooleanLike], default: true},
|
||||
"Mute notifications in addition to statuses? Defaults to `true`."
|
||||
),
|
||||
Operation.parameter(
|
||||
:duration,
|
||||
:query,
|
||||
%Schema{type: :integer},
|
||||
"Expire the mute in `duration` seconds. Default 0 for infinity"
|
||||
),
|
||||
Operation.parameter(
|
||||
:expires_in,
|
||||
:query,
|
||||
%Schema{type: :integer, default: 0},
|
||||
"Deprecated, use `duration` instead"
|
||||
)
|
||||
],
|
||||
responses: %{
|
||||
|
|
@ -323,16 +317,37 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
|||
tags: ["Account actions"],
|
||||
summary: "Block",
|
||||
operationId: "AccountController.block",
|
||||
requestBody: request_body("Parameters", block_request()),
|
||||
security: [%{"oAuth" => ["follow", "write:blocks"]}],
|
||||
description:
|
||||
"Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
|
||||
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
|
||||
parameters: [
|
||||
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
|
||||
],
|
||||
responses: %{
|
||||
200 => Operation.response("Relationship", "application/json", AccountRelationship)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp block_request do
|
||||
%Schema{
|
||||
title: "AccountBlockRequest",
|
||||
description: "POST body for blocking an account",
|
||||
type: :object,
|
||||
properties: %{
|
||||
duration: %Schema{
|
||||
type: :integer,
|
||||
nullable: true,
|
||||
description: "Expire the mute in `duration` seconds. Default 0 for infinity"
|
||||
}
|
||||
},
|
||||
example: %{
|
||||
"duration" => 86_400
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def unblock_operation do
|
||||
%Operation{
|
||||
tags: ["Account actions"],
|
||||
|
|
|
|||
|
|
@ -127,6 +127,20 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
|
|||
}
|
||||
end
|
||||
|
||||
def download_zip_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji pack administration"],
|
||||
summary: "Download a pack from a URL or an uploaded file",
|
||||
operationId: "PleromaAPI.EmojiPackController.download_zip",
|
||||
security: [%{"oAuth" => ["admin:write"]}],
|
||||
requestBody: request_body("Parameters", download_zip_request(), required: true),
|
||||
responses: %{
|
||||
200 => ok_response(),
|
||||
400 => Operation.response("Bad Request", "application/json", ApiError)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp download_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
|
|
@ -143,6 +157,25 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
|
|||
}
|
||||
end
|
||||
|
||||
defp download_zip_request do
|
||||
%Schema{
|
||||
type: :object,
|
||||
required: [:name],
|
||||
properties: %{
|
||||
url: %Schema{
|
||||
type: :string,
|
||||
format: :uri,
|
||||
description: "URL of the file"
|
||||
},
|
||||
file: %Schema{
|
||||
description: "The uploaded ZIP file",
|
||||
type: :object
|
||||
},
|
||||
name: %Schema{type: :string, format: :uri, description: "Pack Name"}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["Emoji pack administration"],
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
|||
id: FlakeID,
|
||||
locked: %Schema{type: :boolean},
|
||||
mute_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||
block_expires_at: %Schema{type: :string, format: "date-time", nullable: true},
|
||||
note: %Schema{type: :string, format: :html},
|
||||
statuses_count: %Schema{type: :integer},
|
||||
url: %Schema{type: :string, format: :uri},
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ defmodule Pleroma.Web.ApiSpec.Scopes.Compiler do
|
|||
end
|
||||
|
||||
def extract_all_scopes do
|
||||
extract_all_scopes_from(Pleroma.Web.ApiSpec.spec())
|
||||
try do
|
||||
extract_all_scopes_from(Pleroma.Web.ApiSpec.spec())
|
||||
catch
|
||||
_, _ -> []
|
||||
end
|
||||
end
|
||||
|
||||
def extract_all_scopes_from(specs) do
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
require Logger
|
||||
|
||||
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def block(blocked, blocker) do
|
||||
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||
def block(blocked, blocker, params \\ %{}) do
|
||||
with {:ok, block_data, meta} <- Builder.block(blocker, blocked, params),
|
||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, meta ++ [local: true]) do
|
||||
{:ok, block}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -501,8 +501,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
end
|
||||
|
||||
@doc "POST /api/v1/accounts/:id/block"
|
||||
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
||||
with {:ok, _activity} <- CommonAPI.block(blocked, blocker) do
|
||||
def block(
|
||||
%{
|
||||
assigns: %{user: blocker, account: blocked},
|
||||
private: %{open_api_spex: %{body_params: params}}
|
||||
} = conn,
|
||||
_params
|
||||
) do
|
||||
with {:ok, _activity} <- CommonAPI.block(blocked, blocker, params) do
|
||||
render(conn, "relationship.json", user: blocker, target: blocked)
|
||||
else
|
||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||
|
|
@ -623,7 +629,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
|||
users: users,
|
||||
for: user,
|
||||
as: :user,
|
||||
embed_relationships: embed_relationships?(params)
|
||||
embed_relationships: embed_relationships?(params),
|
||||
blocks: true
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Hashtag
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
|
|
@ -120,69 +121,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
|
|||
defp resource_search(:v2, "hashtags", query, options) do
|
||||
tags_path = Endpoint.url() <> "/tag/"
|
||||
|
||||
query
|
||||
|> prepare_tags(options)
|
||||
Hashtag.search(query, options)
|
||||
|> Enum.map(fn tag ->
|
||||
%{name: tag, url: tags_path <> tag}
|
||||
end)
|
||||
end
|
||||
|
||||
defp resource_search(:v1, "hashtags", query, options) do
|
||||
prepare_tags(query, options)
|
||||
end
|
||||
|
||||
defp prepare_tags(query, options) do
|
||||
tags =
|
||||
query
|
||||
|> preprocess_uri_query()
|
||||
|> String.split(~r/[^#\w]+/u, trim: true)
|
||||
|> Enum.uniq_by(&String.downcase/1)
|
||||
|
||||
explicit_tags = Enum.filter(tags, fn tag -> String.starts_with?(tag, "#") end)
|
||||
|
||||
tags =
|
||||
if Enum.any?(explicit_tags) do
|
||||
explicit_tags
|
||||
else
|
||||
tags
|
||||
end
|
||||
|
||||
tags = Enum.map(tags, fn tag -> String.trim_leading(tag, "#") end)
|
||||
|
||||
tags =
|
||||
if Enum.empty?(explicit_tags) && !options[:skip_joined_tag] do
|
||||
add_joined_tag(tags)
|
||||
else
|
||||
tags
|
||||
end
|
||||
|
||||
Pleroma.Pagination.paginate_list(tags, options)
|
||||
end
|
||||
|
||||
defp add_joined_tag(tags) do
|
||||
tags
|
||||
|> Kernel.++([joined_tag(tags)])
|
||||
|> Enum.uniq_by(&String.downcase/1)
|
||||
end
|
||||
|
||||
# If `query` is a URI, returns last component of its path, otherwise returns `query`
|
||||
defp preprocess_uri_query(query) do
|
||||
if query =~ ~r/https?:\/\// do
|
||||
query
|
||||
|> String.trim_trailing("/")
|
||||
|> URI.parse()
|
||||
|> Map.get(:path)
|
||||
|> String.split("/")
|
||||
|> Enum.at(-1)
|
||||
else
|
||||
query
|
||||
end
|
||||
end
|
||||
|
||||
defp joined_tag(tags) do
|
||||
tags
|
||||
|> Enum.map(fn tag -> String.capitalize(tag) end)
|
||||
|> Enum.join()
|
||||
Hashtag.search(query, options)
|
||||
end
|
||||
|
||||
defp with_fallback(f, fallback \\ []) do
|
||||
|
|
@ -190,7 +136,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
|
||||
|
|
|
|||
|
|
@ -584,6 +584,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|
||||
{:error, error} when error in [:unexpected_response, :quota_exceeded, :too_many_requests] ->
|
||||
render_error(conn, :service_unavailable, "Translation service not available")
|
||||
|
||||
_ ->
|
||||
render_error(conn, :internal_server_error, "Translation failed")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -168,9 +168,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
UserRelationship.exists?(
|
||||
user_relationships,
|
||||
:endorsement,
|
||||
target,
|
||||
reading_user,
|
||||
&User.endorses?(&2, &1)
|
||||
target,
|
||||
&User.endorses?(&1, &2)
|
||||
)
|
||||
}
|
||||
end
|
||||
|
|
@ -340,6 +340,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|> maybe_put_unread_notification_count(user, opts[:for])
|
||||
|> maybe_put_email_address(user, opts[:for])
|
||||
|> maybe_put_mute_expires_at(user, opts[:for], opts)
|
||||
|> maybe_put_block_expires_at(user, opts[:for], opts)
|
||||
|> maybe_show_birthday(user, opts[:for])
|
||||
end
|
||||
|
||||
|
|
@ -476,6 +477,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
defp maybe_put_mute_expires_at(data, _, _, _), do: data
|
||||
|
||||
defp maybe_put_block_expires_at(data, %User{} = user, target, %{blocks: true}) do
|
||||
Map.put(
|
||||
data,
|
||||
:block_expires_at,
|
||||
UserRelationship.get_block_expire_date(target, user)
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_put_block_expires_at(data, _, _, _), do: data
|
||||
|
||||
defp maybe_show_birthday(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||
data
|
||||
|> Kernel.put_in([:pleroma, :birthday], user.birthday)
|
||||
|
|
|
|||
|
|
@ -157,7 +157,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
|
|||
"pleroma:bookmark_folders",
|
||||
if Pleroma.Language.LanguageDetector.configured?() do
|
||||
"pleroma:language_detection"
|
||||
end
|
||||
end,
|
||||
"pleroma:block_expiration"
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
|
@ -286,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)
|
||||
|
|
@ -337,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
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|
|||
:import_from_filesystem,
|
||||
:remote,
|
||||
:download,
|
||||
:download_zip,
|
||||
:create,
|
||||
:update,
|
||||
:delete
|
||||
|
|
@ -113,6 +114,27 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|
|||
end
|
||||
end
|
||||
|
||||
def download_zip(
|
||||
%{private: %{open_api_spex: %{body_params: params}}} = conn,
|
||||
_
|
||||
) do
|
||||
name = Map.get(params, :name)
|
||||
|
||||
with :ok <- Pack.download_zip(name, params) do
|
||||
json(conn, "ok")
|
||||
else
|
||||
{:error, error} when is_binary(error) ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: error})
|
||||
|
||||
{:error, _} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Could not process pack"})
|
||||
end
|
||||
end
|
||||
|
||||
def download(
|
||||
%{private: %{open_api_spex: %{body_params: %{url: url, name: name} = params}}} = conn,
|
||||
_
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.PleromaAPI.InstancesController do
|
|||
|
||||
def show(conn, _params) do
|
||||
unreachable =
|
||||
Instances.get_consistently_unreachable()
|
||||
Instances.get_unreachable()
|
||||
|> Map.new(fn {host, date} -> {host, to_string(date)} end)
|
||||
|
||||
json(conn, %{"unreachable" => unreachable})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
pipeline :well_known do
|
||||
plug(:accepts, ["activity+json", "json", "jrd", "jrd+json", "xml", "xrd+xml"])
|
||||
plug(:accepts, ["activity+json", "json", "jrd", "jrd+json", "xml", "xrd+xml", "html"])
|
||||
end
|
||||
|
||||
pipeline :config do
|
||||
|
|
@ -466,6 +466,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/import", EmojiPackController, :import_from_filesystem)
|
||||
get("/remote", EmojiPackController, :remote)
|
||||
post("/download", EmojiPackController, :download)
|
||||
post("/download_zip", EmojiPackController, :download_zip)
|
||||
|
||||
post("/files", EmojiFileController, :create)
|
||||
patch("/files", EmojiFileController, :update)
|
||||
|
|
|
|||
|
|
@ -231,8 +231,8 @@
|
|||
</div>
|
||||
|
||||
<%= for %{data: mention, object: object, from: from} <- @mentions do %>
|
||||
<%# mention START %>
|
||||
<%# user card START %>
|
||||
<% # mention START %>
|
||||
<% # user card START %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid mixed-two-up no-stack"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
|
|
@ -291,7 +291,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# user card END %>
|
||||
<% # user card END %>
|
||||
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
|
|
@ -333,12 +333,12 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# mention END %>
|
||||
<% # mention END %>
|
||||
<% end %>
|
||||
|
||||
<%= if @followers != [] do %>
|
||||
|
||||
<%# new followers header START %>
|
||||
<% # new followers header START %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
|
|
@ -397,10 +397,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# new followers header END %>
|
||||
<% # new followers header END %>
|
||||
|
||||
<%= for %{data: follow, from: from} <- @followers do %>
|
||||
<%# user card START %>
|
||||
<% # user card START %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid mixed-two-up no-stack"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
|
|
@ -459,13 +459,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# user card END %>
|
||||
<% # user card END %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% end %>
|
||||
|
||||
<%# divider start %>
|
||||
<% # divider start %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
|
|
@ -514,7 +514,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%# divider end %>
|
||||
<% # divider end %>
|
||||
|
||||
|
||||
<div style="background-color:transparent;">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<%= for {user, total_statuses, latest_status} <- @users_and_statuses do %>
|
||||
<%# user card START %>
|
||||
<% # user card START %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid mixed-two-up no-stack"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%# user card END %>
|
||||
<% # user card END %>
|
||||
|
||||
<%= if latest_status do %>
|
||||
<div style="background-color:transparent;">
|
||||
|
|
@ -104,7 +104,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%# divider start %>
|
||||
<% # divider start %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
|
|
@ -153,6 +153,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%# divider end %>
|
||||
<%# user card END %>
|
||||
<% # divider end %>
|
||||
<% # user card END %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@
|
|||
<td style="word-break: break-word; vertical-align: top;" valign="top">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]-->
|
||||
|
||||
<%# header %>
|
||||
<% # header %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;">
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
</div>
|
||||
|
||||
|
||||
<%# title %>
|
||||
<% # title %>
|
||||
<%= if @title do %>
|
||||
<div style="background-color:transparent;">
|
||||
<div class="block-grid"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<%= label @form, :scope, Gettext.dpgettext("static_pages", "oauth scopes message", "The following permissions will be granted") %>
|
||||
<div class="scopes">
|
||||
<%= for scope <- @available_scopes do %>
|
||||
<%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
|
||||
<% # Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
|
||||
<%= if scope in @scopes do %>
|
||||
<div class="scope">
|
||||
<%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
regex =
|
||||
if webfinger_domain = Pleroma.Config.get([__MODULE__, :domain]) do
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})/
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@(#{host}|#{webfinger_domain})$/
|
||||
else
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
|
||||
~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}$/
|
||||
end
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource),
|
||||
|
|
|
|||
|
|
@ -41,5 +41,10 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
|
|||
end
|
||||
end
|
||||
|
||||
# Default to JSON when no format is specified or format is not recognized
|
||||
def webfinger(%{assigns: %{format: _format}} = conn, %{"resource" => _resource} = params) do
|
||||
webfinger(put_in(conn.assigns.format, "json"), params)
|
||||
end
|
||||
|
||||
def webfinger(conn, _params), do: send_resp(conn, 400, "Bad Request")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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,27 @@ defmodule Pleroma.Workers.DeleteWorker do
|
|||
end
|
||||
|
||||
def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do
|
||||
Instance.perform(:delete_instance, host)
|
||||
# Schedule the per-user deletion jobs
|
||||
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)
|
||||
|
||||
# Delete the instance from the Instances table
|
||||
case Pleroma.Repo.get_by(Pleroma.Instances.Instance, host: host) do
|
||||
nil -> :ok
|
||||
instance -> Pleroma.Repo.delete(instance)
|
||||
end
|
||||
|
||||
# Delete any pending ReachabilityWorker jobs for this domain
|
||||
Pleroma.Workers.ReachabilityWorker.delete_jobs_for_host(host)
|
||||
|
||||
:ok
|
||||
end)
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -5,9 +5,13 @@
|
|||
defmodule Pleroma.Workers.MuteExpireWorker do
|
||||
use Oban.Worker, queue: :background
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
|
||||
Pleroma.User.unmute(muter_id, mutee_id)
|
||||
def perform(%Job{
|
||||
args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}
|
||||
}) do
|
||||
User.unmute(muter_id, mutee_id)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
|
@ -18,6 +22,17 @@ defmodule Pleroma.Workers.MuteExpireWorker do
|
|||
:ok
|
||||
end
|
||||
|
||||
def perform(%Job{
|
||||
args: %{"op" => "unblock_user", "blocker_id" => blocker_id, "blocked_id" => blocked_id}
|
||||
}) do
|
||||
Pleroma.Web.CommonAPI.unblock(
|
||||
User.get_cached_by_id(blocked_id),
|
||||
User.get_cached_by_id(blocker_id)
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@impl true
|
||||
def timeout(_job), do: :timer.seconds(5)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
|
||||
defmodule Pleroma.Workers.PublisherWorker do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
use Oban.Worker, queue: :federator_outgoing, max_attempts: 5
|
||||
use Oban.Worker, queue: :federator_outgoing, max_attempts: 13
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
|
||||
|
|
@ -14,9 +15,30 @@ defmodule Pleroma.Workers.PublisherWorker do
|
|||
Federator.perform(:publish, activity)
|
||||
end
|
||||
|
||||
def perform(%Job{args: %{"op" => "publish_one", "params" => params}}) do
|
||||
def perform(%Job{args: %{"op" => "publish_one", "params" => params}} = job) do
|
||||
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
|
||||
Federator.perform(:publish_one, params)
|
||||
|
||||
# Cancel / skip the job if this server believed to be unreachable now
|
||||
if not Instances.reachable?(params.inbox) do
|
||||
{:cancel, :unreachable}
|
||||
else
|
||||
case Federator.perform(:publish_one, params) do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
{:error, _} = error ->
|
||||
# Only mark as unreachable on final failure
|
||||
if job.attempt == job.max_attempts do
|
||||
Instances.set_unreachable(params.inbox)
|
||||
end
|
||||
|
||||
error
|
||||
|
||||
error ->
|
||||
# Unexpected error, may have been client side
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
116
lib/pleroma/workers/reachability_worker.ex
Normal file
116
lib/pleroma/workers/reachability_worker.ex
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.ReachabilityWorker do
|
||||
use Oban.Worker,
|
||||
queue: :background,
|
||||
max_attempts: 1,
|
||||
unique: [period: :infinity, states: [:available, :scheduled], keys: [:domain]]
|
||||
|
||||
alias Pleroma.HTTP
|
||||
alias Pleroma.Instances
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
@impl true
|
||||
def perform(%Oban.Job{args: %{"domain" => domain, "phase" => phase, "attempt" => attempt}}) do
|
||||
case check_reachability(domain) do
|
||||
:ok ->
|
||||
Instances.set_reachable("https://#{domain}")
|
||||
:ok
|
||||
|
||||
{:error, _} = error ->
|
||||
handle_failed_attempt(domain, phase, attempt)
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
# New jobs enter here and are immediately re-scheduled for the first phase
|
||||
@impl true
|
||||
def perform(%Oban.Job{args: %{"domain" => domain}}) do
|
||||
scheduled_at = DateTime.add(DateTime.utc_now(), 60, :second)
|
||||
|
||||
%{
|
||||
"domain" => domain,
|
||||
"phase" => "phase_1min",
|
||||
"attempt" => 1
|
||||
}
|
||||
|> new(scheduled_at: scheduled_at, replace: true)
|
||||
|> Oban.insert()
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@impl true
|
||||
def timeout(_job), do: :timer.seconds(5)
|
||||
|
||||
@doc "Deletes scheduled jobs to check reachability for specified instance"
|
||||
def delete_jobs_for_host(host) do
|
||||
Oban.Job
|
||||
|> where(worker: "Pleroma.Workers.ReachabilityWorker")
|
||||
|> where([j], j.args["domain"] == ^host)
|
||||
|> Oban.delete_all_jobs()
|
||||
end
|
||||
|
||||
defp check_reachability(domain) do
|
||||
case HTTP.get("https://#{domain}/") do
|
||||
{:ok, %{status: status}} when status in 200..299 ->
|
||||
:ok
|
||||
|
||||
{:ok, %{status: _status}} ->
|
||||
{:error, :unreachable}
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_failed_attempt(_domain, "final", _attempt), do: :ok
|
||||
|
||||
defp handle_failed_attempt(domain, phase, attempt) do
|
||||
{interval_minutes, max_attempts, next_phase} = get_phase_config(phase)
|
||||
|
||||
if attempt >= max_attempts do
|
||||
# Move to next phase
|
||||
schedule_next_phase(domain, next_phase)
|
||||
else
|
||||
# Retry same phase with incremented attempt
|
||||
schedule_retry(domain, phase, attempt + 1, interval_minutes)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_phase_config("phase_1min"), do: {1, 4, "phase_15min"}
|
||||
defp get_phase_config("phase_15min"), do: {15, 4, "phase_1hour"}
|
||||
defp get_phase_config("phase_1hour"), do: {60, 4, "phase_8hour"}
|
||||
defp get_phase_config("phase_8hour"), do: {480, 4, "phase_24hour"}
|
||||
defp get_phase_config("phase_24hour"), do: {1440, 4, "final"}
|
||||
defp get_phase_config("final"), do: {nil, 0, nil}
|
||||
|
||||
defp schedule_next_phase(_domain, "final"), do: :ok
|
||||
|
||||
defp schedule_next_phase(domain, next_phase) do
|
||||
{interval_minutes, _max_attempts, _next_phase} = get_phase_config(next_phase)
|
||||
scheduled_at = DateTime.add(DateTime.utc_now(), interval_minutes * 60, :second)
|
||||
|
||||
%{
|
||||
"domain" => domain,
|
||||
"phase" => next_phase,
|
||||
"attempt" => 1
|
||||
}
|
||||
|> new(scheduled_at: scheduled_at, replace: true)
|
||||
|> Oban.insert()
|
||||
end
|
||||
|
||||
def schedule_retry(domain, phase, attempt, interval_minutes) do
|
||||
scheduled_at = DateTime.add(DateTime.utc_now(), interval_minutes * 60, :second)
|
||||
|
||||
%{
|
||||
"domain" => domain,
|
||||
"phase" => phase,
|
||||
"attempt" => attempt
|
||||
}
|
||||
|> new(scheduled_at: scheduled_at, replace: true)
|
||||
|> Oban.insert()
|
||||
end
|
||||
end
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.ReceiverWorker do
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Federator
|
||||
|
|
@ -37,6 +38,11 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
{:ok, _public_key} <- Signature.refetch_public_key(conn_data),
|
||||
{:signature, true} <- {:signature, Signature.validate_signature(conn_data)},
|
||||
{:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
|
||||
unless Instances.reachable?(params["actor"]) do
|
||||
domain = URI.parse(params["actor"]).host
|
||||
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
|
||||
end
|
||||
|
||||
{:ok, res}
|
||||
else
|
||||
e -> process_errors(e)
|
||||
|
|
@ -45,6 +51,11 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
|
||||
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
|
||||
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
|
||||
unless Instances.reachable?(params["actor"]) do
|
||||
domain = URI.parse(params["actor"]).host
|
||||
Oban.insert(Pleroma.Workers.ReachabilityWorker.new(%{"domain" => domain}))
|
||||
end
|
||||
|
||||
{:ok, res}
|
||||
else
|
||||
e -> process_errors(e)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Object.Fetcher
|
||||
|
||||
use Oban.Worker, queue: :background, unique: [period: :infinity]
|
||||
|
|
@ -11,6 +12,11 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
|
|||
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
||||
case Fetcher.fetch_object_from_id(id, depth: args["depth"]) do
|
||||
{:ok, _object} ->
|
||||
unless Instances.reachable?(id) do
|
||||
# Mark the server as reachable since we successfully fetched an object
|
||||
Instances.set_reachable(id)
|
||||
end
|
||||
|
||||
:ok
|
||||
|
||||
{:allowed_depth, false} ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue